xref: /reactos/drivers/filesystems/ntfs/mft.c (revision fb5d5ecd)
1 /*
2  *  ReactOS kernel
3  *  Copyright (C) 2002, 2014 ReactOS Team
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * COPYRIGHT:        See COPYING in the top level directory
20  * PROJECT:          ReactOS kernel
21  * FILE:             drivers/filesystem/ntfs/mft.c
22  * PURPOSE:          NTFS filesystem driver
23  * PROGRAMMERS:      Eric Kohl
24  *                   Valentin Verkhovsky
25  *                   Pierre Schweitzer (pierre@reactos.org)
26  *                   Hervé Poussineau (hpoussin@reactos.org)
27  *                   Trevor Thompson
28  */
29 
30 /* INCLUDES *****************************************************************/
31 
32 #include "ntfs.h"
33 #include <ntintsafe.h>
34 
35 #define NDEBUG
36 #include <debug.h>
37 
38 /* FUNCTIONS ****************************************************************/
39 
40 PNTFS_ATTR_CONTEXT
41 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
42 {
43     PNTFS_ATTR_CONTEXT Context;
44 
45     Context = ExAllocateFromNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList);
46     if(!Context)
47     {
48         DPRINT1("Error: Unable to allocate memory for context!\n");
49         return NULL;
50     }
51 
52     // Allocate memory for a copy of the attribute
53     Context->pRecord = ExAllocatePoolWithTag(NonPagedPool, AttrRecord->Length, TAG_NTFS);
54     if(!Context->pRecord)
55     {
56         DPRINT1("Error: Unable to allocate memory for attribute record!\n");
57         ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context);
58         return NULL;
59     }
60 
61     // Copy the attribute
62     RtlCopyMemory(Context->pRecord, AttrRecord, AttrRecord->Length);
63 
64     if (AttrRecord->IsNonResident)
65     {
66         LONGLONG DataRunOffset;
67         ULONGLONG DataRunLength;
68         ULONGLONG NextVBN = 0;
69         PUCHAR DataRun = (PUCHAR)((ULONG_PTR)Context->pRecord + Context->pRecord->NonResident.MappingPairsOffset);
70 
71         Context->CacheRun = DataRun;
72         Context->CacheRunOffset = 0;
73         Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
74         Context->CacheRunLength = DataRunLength;
75         if (DataRunOffset != -1)
76         {
77             /* Normal run. */
78             Context->CacheRunStartLCN =
79             Context->CacheRunLastLCN = DataRunOffset;
80         }
81         else
82         {
83             /* Sparse run. */
84             Context->CacheRunStartLCN = -1;
85             Context->CacheRunLastLCN = 0;
86         }
87         Context->CacheRunCurrentOffset = 0;
88 
89         // Convert the data runs to a map control block
90         if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN)))
91         {
92             DPRINT1("Unable to convert data runs to MCB!\n");
93             ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
94             ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context);
95             return NULL;
96         }
97     }
98 
99     return Context;
100 }
101 
102 
103 VOID
104 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
105 {
106     if (Context->pRecord)
107     {
108         if (Context->pRecord->IsNonResident)
109         {
110             FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
111         }
112 
113         ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
114     }
115 
116     ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context);
117 }
118 
119 
120 /**
121 * @name FindAttribute
122 * @implemented
123 *
124 * Searches a file record for an attribute matching the given type and name.
125 *
126 * @param Offset
127 * Optional pointer to a ULONG that will receive the offset of the found attribute
128 * from the beginning of the record. Can be set to NULL.
129 */
130 NTSTATUS
131 FindAttribute(PDEVICE_EXTENSION Vcb,
132               PFILE_RECORD_HEADER MftRecord,
133               ULONG Type,
134               PCWSTR Name,
135               ULONG NameLength,
136               PNTFS_ATTR_CONTEXT * AttrCtx,
137               PULONG Offset)
138 {
139     BOOLEAN Found;
140     NTSTATUS Status;
141     FIND_ATTR_CONTXT Context;
142     PNTFS_ATTR_RECORD Attribute;
143 
144     DPRINT("FindAttribute(%p, %p, 0x%x, %S, %lu, %p, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx, Offset);
145 
146     Found = FALSE;
147     Status = FindFirstAttribute(&Context, Vcb, MftRecord, FALSE, &Attribute);
148     while (NT_SUCCESS(Status))
149     {
150         if (Attribute->Type == Type && Attribute->NameLength == NameLength)
151         {
152             if (NameLength != 0)
153             {
154                 PWCHAR AttrName;
155 
156                 AttrName = (PWCHAR)((PCHAR)Attribute + Attribute->NameOffset);
157                 DPRINT("%.*S, %.*S\n", Attribute->NameLength, AttrName, NameLength, Name);
158                 if (RtlCompareMemory(AttrName, Name, NameLength * sizeof(WCHAR)) == (NameLength  * sizeof(WCHAR)))
159                 {
160                     Found = TRUE;
161                 }
162             }
163             else
164             {
165                 Found = TRUE;
166             }
167 
168             if (Found)
169             {
170                 /* Found it, fill up the context and return. */
171                 DPRINT("Found context\n");
172                 *AttrCtx = PrepareAttributeContext(Attribute);
173 
174                 (*AttrCtx)->FileMFTIndex = MftRecord->MFTRecordNumber;
175 
176                 if (Offset != NULL)
177                     *Offset = Context.Offset;
178 
179                 FindCloseAttribute(&Context);
180                 return STATUS_SUCCESS;
181             }
182         }
183 
184         Status = FindNextAttribute(&Context, &Attribute);
185     }
186 
187     FindCloseAttribute(&Context);
188     return STATUS_OBJECT_NAME_NOT_FOUND;
189 }
190 
191 
192 ULONGLONG
193 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord)
194 {
195     if (AttrRecord->IsNonResident)
196         return AttrRecord->NonResident.AllocatedSize;
197     else
198         return ALIGN_UP_BY(AttrRecord->Resident.ValueLength, ATTR_RECORD_ALIGNMENT);
199 }
200 
201 
202 ULONGLONG
203 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
204 {
205     if (AttrRecord->IsNonResident)
206         return AttrRecord->NonResident.DataSize;
207     else
208         return AttrRecord->Resident.ValueLength;
209 }
210 
211 /**
212 * @name IncreaseMftSize
213 * @implemented
214 *
215 * Increases the size of the master file table on a volume, increasing the space available for file records.
216 *
217 * @param Vcb
218 * Pointer to the VCB (DEVICE_EXTENSION) of the target volume.
219 *
220 *
221 * @param CanWait
222 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
223 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
224 *
225 * @return
226 * STATUS_SUCCESS on success.
227 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
228 * STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap.
229 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
230 *
231 * @remarks
232 * Increases the size of the Master File Table by 64 records. Bitmap entries for the new records are cleared,
233 * and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO.
234 * This function will wait for exlusive access to the volume fcb.
235 */
236 NTSTATUS
237 IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
238 {
239     PNTFS_ATTR_CONTEXT BitmapContext;
240     LARGE_INTEGER BitmapSize;
241     LARGE_INTEGER DataSize;
242     LONGLONG BitmapSizeDifference;
243     ULONG NewRecords = ATTR_RECORD_ALIGNMENT * 8;  // Allocate one new record for every bit of every byte we'll be adding to the bitmap
244     ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * NewRecords;
245     ULONG BitmapOffset;
246     PUCHAR BitmapBuffer;
247     ULONGLONG BitmapBytes;
248     ULONGLONG NewBitmapSize;
249     ULONGLONG FirstNewMftIndex;
250     ULONG BytesRead;
251     ULONG LengthWritten;
252     PFILE_RECORD_HEADER BlankFileRecord;
253     ULONG i;
254     NTSTATUS Status;
255 
256     DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb, CanWait ? "TRUE" : "FALSE");
257 
258     // We need exclusive access to the mft while we change its size
259     if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), CanWait))
260     {
261         return STATUS_CANT_WAIT;
262     }
263 
264     // Create a blank file record that will be used later
265     BlankFileRecord = NtfsCreateEmptyFileRecord(Vcb);
266     if (!BlankFileRecord)
267     {
268         DPRINT1("Error: Unable to create empty file record!\n");
269         return STATUS_INSUFFICIENT_RESOURCES;
270     }
271 
272     // Clear the flags (file record is not in use)
273     BlankFileRecord->Flags = 0;
274 
275     // Find the bitmap attribute of master file table
276     Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
277     if (!NT_SUCCESS(Status))
278     {
279         DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
280         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
281         ExReleaseResourceLite(&(Vcb->DirResource));
282         return Status;
283     }
284 
285     // Get size of Bitmap Attribute
286     BitmapSize.QuadPart = AttributeDataLength(BitmapContext->pRecord);
287 
288     // Calculate the new mft size
289     DataSize.QuadPart = AttributeDataLength(Vcb->MFTContext->pRecord) + DataSizeDifference;
290 
291     // Find the index of the first Mft entry that will be created
292     FirstNewMftIndex = AttributeDataLength(Vcb->MFTContext->pRecord) / Vcb->NtfsInfo.BytesPerFileRecord;
293 
294     // Determine how many bytes will make up the bitmap
295     BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8;
296     if ((DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord) % 8 != 0)
297         BitmapBytes++;
298 
299     // Windows will always keep the number of bytes in a bitmap as a multiple of 8, so no bytes are wasted on slack
300     BitmapBytes = ALIGN_UP_BY(BitmapBytes, ATTR_RECORD_ALIGNMENT);
301 
302     // Determine how much we need to adjust the bitmap size (it's possible we don't)
303     BitmapSizeDifference = BitmapBytes - BitmapSize.QuadPart;
304     NewBitmapSize = max(BitmapSize.QuadPart + BitmapSizeDifference, BitmapSize.QuadPart);
305 
306     // Allocate memory for the bitmap
307     BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, NewBitmapSize, TAG_NTFS);
308     if (!BitmapBuffer)
309     {
310         DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
311         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
312         ExReleaseResourceLite(&(Vcb->DirResource));
313         ReleaseAttributeContext(BitmapContext);
314         return STATUS_INSUFFICIENT_RESOURCES;
315     }
316 
317     // Zero the bytes we'll be adding
318     RtlZeroMemory(BitmapBuffer, NewBitmapSize);
319 
320     // Read the bitmap attribute
321     BytesRead = ReadAttribute(Vcb,
322                               BitmapContext,
323                               0,
324                               (PCHAR)BitmapBuffer,
325                               BitmapSize.LowPart);
326     if (BytesRead != BitmapSize.LowPart)
327     {
328         DPRINT1("ERROR: Bytes read != Bitmap size!\n");
329         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
330         ExReleaseResourceLite(&(Vcb->DirResource));
331         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
332         ReleaseAttributeContext(BitmapContext);
333         return STATUS_INVALID_PARAMETER;
334     }
335 
336     // Increase the mft size
337     Status = SetNonResidentAttributeDataLength(Vcb, Vcb->MFTContext, Vcb->MftDataOffset, Vcb->MasterFileTable, &DataSize);
338     if (!NT_SUCCESS(Status))
339     {
340         DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
341         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
342         ExReleaseResourceLite(&(Vcb->DirResource));
343         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
344         ReleaseAttributeContext(BitmapContext);
345         return Status;
346     }
347 
348     // We'll need to find the bitmap again, because its offset will have changed after resizing the data attribute
349     ReleaseAttributeContext(BitmapContext);
350     Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset);
351     if (!NT_SUCCESS(Status))
352     {
353         DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
354         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
355         ExReleaseResourceLite(&(Vcb->DirResource));
356         return Status;
357     }
358 
359     // If the bitmap grew
360     if (BitmapSizeDifference > 0)
361     {
362         // Set the new bitmap size
363         BitmapSize.QuadPart = NewBitmapSize;
364         if (BitmapContext->pRecord->IsNonResident)
365             Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
366         else
367             Status = SetResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
368 
369         if (!NT_SUCCESS(Status))
370         {
371             DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
372             ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
373             ExReleaseResourceLite(&(Vcb->DirResource));
374             ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
375             ReleaseAttributeContext(BitmapContext);
376             return Status;
377         }
378     }
379 
380     NtfsDumpFileAttributes(Vcb, Vcb->MasterFileTable);
381 
382     // Update the file record with the new attribute sizes
383     Status = UpdateFileRecord(Vcb, Vcb->VolumeFcb->MFTIndex, Vcb->MasterFileTable);
384     if (!NT_SUCCESS(Status))
385     {
386         DPRINT1("ERROR: Failed to update $MFT file record!\n");
387         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
388         ExReleaseResourceLite(&(Vcb->DirResource));
389         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
390         ReleaseAttributeContext(BitmapContext);
391         return Status;
392     }
393 
394     // Write out the new bitmap
395     Status = WriteAttribute(Vcb, BitmapContext, 0, BitmapBuffer, NewBitmapSize, &LengthWritten, Vcb->MasterFileTable);
396     if (!NT_SUCCESS(Status))
397     {
398         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
399         ExReleaseResourceLite(&(Vcb->DirResource));
400         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
401         ReleaseAttributeContext(BitmapContext);
402         DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n");
403         return Status;
404     }
405 
406     // Create blank records for the new file record entries.
407     for (i = 0; i < NewRecords; i++)
408     {
409         Status = UpdateFileRecord(Vcb, FirstNewMftIndex + i, BlankFileRecord);
410         if (!NT_SUCCESS(Status))
411         {
412             DPRINT1("ERROR: Failed to write blank file record!\n");
413             ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
414             ExReleaseResourceLite(&(Vcb->DirResource));
415             ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
416             ReleaseAttributeContext(BitmapContext);
417             return Status;
418         }
419     }
420 
421     // Update the mft mirror
422     Status = UpdateMftMirror(Vcb);
423 
424     // Cleanup
425     ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
426     ExReleaseResourceLite(&(Vcb->DirResource));
427     ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
428     ReleaseAttributeContext(BitmapContext);
429 
430     return Status;
431 }
432 
433 /**
434 * @name MoveAttributes
435 * @implemented
436 *
437 * Moves a block of attributes to a new location in the file Record. The attribute at FirstAttributeToMove
438 * and every attribute after that will be moved to MoveTo.
439 *
440 * @param DeviceExt
441 * Pointer to the DEVICE_EXTENSION (VCB) of the target volume.
442 *
443 * @param FirstAttributeToMove
444 * Pointer to the first NTFS_ATTR_RECORD that needs to be moved. This pointer must reside within a file record.
445 *
446 * @param FirstAttributeOffset
447 * Offset of FirstAttributeToMove relative to the beginning of the file record.
448 *
449 * @param MoveTo
450 * ULONG_PTR with the memory location that will be the new location of the first attribute being moved.
451 *
452 * @return
453 * The new location of the final attribute (i.e. AttributeEnd marker).
454 */
455 PNTFS_ATTR_RECORD
456 MoveAttributes(PDEVICE_EXTENSION DeviceExt,
457                PNTFS_ATTR_RECORD FirstAttributeToMove,
458                ULONG FirstAttributeOffset,
459                ULONG_PTR MoveTo)
460 {
461     // Get the size of all attributes after this one
462     ULONG MemBlockSize = 0;
463     PNTFS_ATTR_RECORD CurrentAttribute = FirstAttributeToMove;
464     ULONG CurrentOffset = FirstAttributeOffset;
465     PNTFS_ATTR_RECORD FinalAttribute;
466 
467     while (CurrentAttribute->Type != AttributeEnd && CurrentOffset < DeviceExt->NtfsInfo.BytesPerFileRecord)
468     {
469         CurrentOffset += CurrentAttribute->Length;
470         MemBlockSize += CurrentAttribute->Length;
471         CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
472     }
473 
474     FinalAttribute = (PNTFS_ATTR_RECORD)(MoveTo + MemBlockSize);
475     MemBlockSize += sizeof(ULONG) * 2;  // Add the AttributeEnd and file record end
476 
477     ASSERT(MemBlockSize % ATTR_RECORD_ALIGNMENT == 0);
478 
479     // Move the attributes after this one
480     RtlMoveMemory((PCHAR)MoveTo, FirstAttributeToMove, MemBlockSize);
481 
482     return FinalAttribute;
483 }
484 
485 NTSTATUS
486 InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt,
487                                    PNTFS_ATTR_CONTEXT AttrContext,
488                                    PFILE_RECORD_HEADER FileRecord,
489                                    ULONG AttrOffset,
490                                    ULONG DataSize)
491 {
492     PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
493     PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
494     PNTFS_ATTR_RECORD FinalAttribute;
495     ULONG OldAttributeLength = Destination->Length;
496     ULONG NextAttributeOffset;
497 
498     DPRINT1("InternalSetResidentAttributeLength( %p, %p, %p, %lu, %lu )\n", DeviceExt, AttrContext, FileRecord, AttrOffset, DataSize);
499 
500     ASSERT(!AttrContext->pRecord->IsNonResident);
501 
502     // Update ValueLength Field
503     Destination->Resident.ValueLength = DataSize;
504 
505     // Calculate the record length and end marker offset
506     Destination->Length = ALIGN_UP_BY(DataSize + AttrContext->pRecord->Resident.ValueOffset, ATTR_RECORD_ALIGNMENT);
507     NextAttributeOffset = AttrOffset + Destination->Length;
508 
509     // Ensure NextAttributeOffset is aligned to an 8-byte boundary
510     ASSERT(NextAttributeOffset % ATTR_RECORD_ALIGNMENT == 0);
511 
512     // Will the new attribute be larger than the old one?
513     if (Destination->Length > OldAttributeLength)
514     {
515         // Free the old copy of the attribute in the context, as it will be the wrong length
516         ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
517 
518         // Create a new copy of the attribute record for the context
519         AttrContext->pRecord = ExAllocatePoolWithTag(NonPagedPool, Destination->Length, TAG_NTFS);
520         if (!AttrContext->pRecord)
521         {
522             DPRINT1("Unable to allocate memory for attribute!\n");
523             return STATUS_INSUFFICIENT_RESOURCES;
524         }
525         RtlZeroMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + OldAttributeLength), Destination->Length - OldAttributeLength);
526         RtlCopyMemory(AttrContext->pRecord, Destination, OldAttributeLength);
527     }
528 
529     // Are there attributes after this one that need to be moved?
530     if (NextAttribute->Type != AttributeEnd)
531     {
532         // Move the attributes after this one
533         FinalAttribute = MoveAttributes(DeviceExt, NextAttribute, NextAttributeOffset, (ULONG_PTR)Destination + Destination->Length);
534     }
535     else
536     {
537         // advance to the final "attribute," adjust for the changed length of the attribute we're resizing
538         FinalAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute - OldAttributeLength + Destination->Length);
539     }
540 
541     // Update pRecord's length
542     AttrContext->pRecord->Length = Destination->Length;
543     AttrContext->pRecord->Resident.ValueLength = DataSize;
544 
545     // set the file record end
546     SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END);
547 
548     //NtfsDumpFileRecord(DeviceExt, FileRecord);
549 
550     return STATUS_SUCCESS;
551 }
552 
553 /**
554 *   @parameter FileRecord
555 *   Pointer to a file record. Must be a full record at least
556 *   Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
557 */
558 NTSTATUS
559 SetAttributeDataLength(PFILE_OBJECT FileObject,
560                        PNTFS_FCB Fcb,
561                        PNTFS_ATTR_CONTEXT AttrContext,
562                        ULONG AttrOffset,
563                        PFILE_RECORD_HEADER FileRecord,
564                        PLARGE_INTEGER DataSize)
565 {
566     NTSTATUS Status = STATUS_SUCCESS;
567 
568     DPRINT1("SetAttributeDataLength(%p, %p, %p, %lu, %p, %I64u)\n",
569             FileObject,
570             Fcb,
571             AttrContext,
572             AttrOffset,
573             FileRecord,
574             DataSize->QuadPart);
575 
576     // are we truncating the file?
577     if (DataSize->QuadPart < AttributeDataLength(AttrContext->pRecord))
578     {
579         if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize))
580         {
581             DPRINT1("Can't truncate a memory-mapped file!\n");
582             return STATUS_USER_MAPPED_FILE;
583         }
584     }
585 
586     if (AttrContext->pRecord->IsNonResident)
587     {
588         Status = SetNonResidentAttributeDataLength(Fcb->Vcb,
589                                                    AttrContext,
590                                                    AttrOffset,
591                                                    FileRecord,
592                                                    DataSize);
593     }
594     else
595     {
596         // resident attribute
597         Status = SetResidentAttributeDataLength(Fcb->Vcb,
598                                                 AttrContext,
599                                                 AttrOffset,
600                                                 FileRecord,
601                                                 DataSize);
602     }
603 
604     if (!NT_SUCCESS(Status))
605     {
606         DPRINT1("ERROR: Failed to set size of attribute!\n");
607         return Status;
608     }
609 
610     //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
611 
612     // write the updated file record back to disk
613     Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord);
614 
615     if (NT_SUCCESS(Status))
616     {
617         if (AttrContext->pRecord->IsNonResident)
618             Fcb->RFCB.AllocationSize.QuadPart = AttrContext->pRecord->NonResident.AllocatedSize;
619         else
620             Fcb->RFCB.AllocationSize = *DataSize;
621         Fcb->RFCB.FileSize = *DataSize;
622         Fcb->RFCB.ValidDataLength = *DataSize;
623         CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
624     }
625 
626     return STATUS_SUCCESS;
627 }
628 
629 /**
630 * @name SetFileRecordEnd
631 * @implemented
632 *
633 * This small function sets a new endpoint for the file record. It set's the final
634 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
635 *
636 * @param FileRecord
637 * Pointer to the file record whose endpoint (length) will be set.
638 *
639 * @param AttrEnd
640 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
641 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
642 *
643 * @param EndMarker
644 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
645 * a file record, it preserves the final ULONG that previously ended the record, even though this
646 * value is (to my knowledge) never used. We emulate this behavior.
647 *
648 */
649 VOID
650 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
651                  PNTFS_ATTR_RECORD AttrEnd,
652                  ULONG EndMarker)
653 {
654     // Ensure AttrEnd is aligned on an 8-byte boundary, relative to FileRecord
655     ASSERT(((ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord) % ATTR_RECORD_ALIGNMENT == 0);
656 
657     // mark the end of attributes
658     AttrEnd->Type = AttributeEnd;
659 
660     // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
661     AttrEnd->Length = EndMarker;
662 
663     // recalculate bytes in use
664     FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2;
665 }
666 
667 /**
668 * @name SetNonResidentAttributeDataLength
669 * @implemented
670 *
671 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
672 *
673 * @param Vcb
674 * Pointer to a DEVICE_EXTENSION describing the target disk.
675 *
676 * @param AttrContext
677 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
678 *
679 * @param AttrOffset
680 * Offset, from the beginning of the record, of the attribute being sized.
681 *
682 * @param FileRecord
683 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
684 * not just the header.
685 *
686 * @param DataSize
687 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
688 *
689 * @return
690 * STATUS_SUCCESS on success;
691 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
692 * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
693 *
694 * @remarks
695 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
696 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
697 * any associated files. Synchronization is the callers responsibility.
698 */
699 NTSTATUS
700 SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
701                                   PNTFS_ATTR_CONTEXT AttrContext,
702                                   ULONG AttrOffset,
703                                   PFILE_RECORD_HEADER FileRecord,
704                                   PLARGE_INTEGER DataSize)
705 {
706     NTSTATUS Status = STATUS_SUCCESS;
707     ULONG BytesPerCluster = Vcb->NtfsInfo.BytesPerCluster;
708     ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster);
709     PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
710     ULONG ExistingClusters = AttrContext->pRecord->NonResident.AllocatedSize / BytesPerCluster;
711 
712     ASSERT(AttrContext->pRecord->IsNonResident);
713 
714     // do we need to increase the allocation size?
715     if (AttrContext->pRecord->NonResident.AllocatedSize < AllocationSize)
716     {
717         ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters;
718         LARGE_INTEGER LastClusterInDataRun;
719         ULONG NextAssignedCluster;
720         ULONG AssignedClusters;
721 
722         if (ExistingClusters == 0)
723         {
724             LastClusterInDataRun.QuadPart = 0;
725         }
726         else
727         {
728             if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
729                                           (LONGLONG)AttrContext->pRecord->NonResident.HighestVCN,
730                                           (PLONGLONG)&LastClusterInDataRun.QuadPart,
731                                           NULL,
732                                           NULL,
733                                           NULL,
734                                           NULL))
735             {
736                 DPRINT1("Error looking up final large MCB entry!\n");
737 
738                 // Most likely, HighestVCN went above the largest mapping
739                 DPRINT1("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN);
740                 return STATUS_INVALID_PARAMETER;
741             }
742         }
743 
744         DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
745         DPRINT("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN);
746 
747         while (ClustersNeeded > 0)
748         {
749             Status = NtfsAllocateClusters(Vcb,
750                                           LastClusterInDataRun.LowPart + 1,
751                                           ClustersNeeded,
752                                           &NextAssignedCluster,
753                                           &AssignedClusters);
754 
755             if (!NT_SUCCESS(Status))
756             {
757                 DPRINT1("Error: Unable to allocate requested clusters!\n");
758                 return Status;
759             }
760 
761             // now we need to add the clusters we allocated to the data run
762             Status = AddRun(Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters);
763             if (!NT_SUCCESS(Status))
764             {
765                 DPRINT1("Error: Unable to add data run!\n");
766                 return Status;
767             }
768 
769             ClustersNeeded -= AssignedClusters;
770             LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1;
771         }
772     }
773     else if (AttrContext->pRecord->NonResident.AllocatedSize > AllocationSize)
774     {
775         // shrink allocation size
776         ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster);
777         Status = FreeClusters(Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree);
778     }
779 
780     // TODO: is the file compressed, encrypted, or sparse?
781 
782     AttrContext->pRecord->NonResident.AllocatedSize = AllocationSize;
783     AttrContext->pRecord->NonResident.DataSize = DataSize->QuadPart;
784     AttrContext->pRecord->NonResident.InitializedSize = DataSize->QuadPart;
785 
786     DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
787     DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
788     DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
789 
790     // HighestVCN seems to be set incorrectly somewhere. Apply a hack-fix to reset it.
791     // HACKHACK FIXME: Fix for sparse files; this math won't work in that case.
792     AttrContext->pRecord->NonResident.HighestVCN = ((ULONGLONG)AllocationSize / Vcb->NtfsInfo.BytesPerCluster) - 1;
793     DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN;
794 
795     DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
796 
797     return Status;
798 }
799 
800 /**
801 * @name SetResidentAttributeDataLength
802 * @implemented
803 *
804 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
805 *
806 * @param Vcb
807 * Pointer to a DEVICE_EXTENSION describing the target disk.
808 *
809 * @param AttrContext
810 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
811 *
812 * @param AttrOffset
813 * Offset, from the beginning of the record, of the attribute being sized.
814 *
815 * @param FileRecord
816 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
817 * not just the header.
818 *
819 * @param DataSize
820 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
821 *
822 * @return
823 * STATUS_SUCCESS on success;
824 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
825 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
826 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
827 * last attribute listed in the file record.
828 *
829 * @remarks
830 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
831 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
832 * any associated files. Synchronization is the callers responsibility.
833 */
834 NTSTATUS
835 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
836                                PNTFS_ATTR_CONTEXT AttrContext,
837                                ULONG AttrOffset,
838                                PFILE_RECORD_HEADER FileRecord,
839                                PLARGE_INTEGER DataSize)
840 {
841     NTSTATUS Status;
842 
843     // find the next attribute
844     ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
845     PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
846 
847     ASSERT(!AttrContext->pRecord->IsNonResident);
848 
849     //NtfsDumpFileAttributes(Vcb, FileRecord);
850 
851     // Do we need to increase the data length?
852     if (DataSize->QuadPart > AttrContext->pRecord->Resident.ValueLength)
853     {
854         // There's usually padding at the end of a record. Do we need to extend past it?
855         ULONG MaxValueLength = AttrContext->pRecord->Length - AttrContext->pRecord->Resident.ValueOffset;
856         if (MaxValueLength < DataSize->LowPart)
857         {
858             // If this is the last attribute, we could move the end marker to the very end of the file record
859             MaxValueLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
860 
861             if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
862             {
863                 // convert attribute to non-resident
864                 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
865                 PNTFS_ATTR_RECORD NewRecord;
866                 LARGE_INTEGER AttribDataSize;
867                 PVOID AttribData;
868                 ULONG NewRecordLength;
869                 ULONG EndAttributeOffset;
870                 ULONG LengthWritten;
871 
872                 DPRINT1("Converting attribute to non-resident.\n");
873 
874                 AttribDataSize.QuadPart = AttrContext->pRecord->Resident.ValueLength;
875 
876                 // Is there existing data we need to back-up?
877                 if (AttribDataSize.QuadPart > 0)
878                 {
879                     AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
880                     if (AttribData == NULL)
881                     {
882                         DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
883                         return STATUS_INSUFFICIENT_RESOURCES;
884                     }
885 
886                     // read data to temp buffer
887                     Status = ReadAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
888                     if (!NT_SUCCESS(Status))
889                     {
890                         DPRINT1("ERROR: Unable to read attribute before migrating!\n");
891                         ExFreePoolWithTag(AttribData, TAG_NTFS);
892                         return Status;
893                     }
894                 }
895 
896                 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
897 
898                 // The size of a 0-length, non-resident attribute will be 0x41 + the size of the attribute name, aligned to an 8-byte boundary
899                 NewRecordLength = ALIGN_UP_BY(0x41 + (AttrContext->pRecord->NameLength * sizeof(WCHAR)), ATTR_RECORD_ALIGNMENT);
900 
901                 // Create a new attribute record that will store the 0-length, non-resident attribute
902                 NewRecord = ExAllocatePoolWithTag(NonPagedPool, NewRecordLength, TAG_NTFS);
903 
904                 // Zero out the NonResident structure
905                 RtlZeroMemory(NewRecord, NewRecordLength);
906 
907                 // Copy the data that's common to both non-resident and resident attributes
908                 RtlCopyMemory(NewRecord, AttrContext->pRecord, FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.ValueLength));
909 
910                 // if there's a name
911                 if (AttrContext->pRecord->NameLength != 0)
912                 {
913                     // copy the name
914                     // An attribute name will be located at offset 0x18 for a resident attribute, 0x40 for non-resident
915                     RtlCopyMemory((PCHAR)((ULONG_PTR)NewRecord + 0x40),
916                                   (PCHAR)((ULONG_PTR)AttrContext->pRecord + 0x18),
917                                   AttrContext->pRecord->NameLength * sizeof(WCHAR));
918                 }
919 
920                 // update the mapping pairs offset, which will be 0x40 (size of a non-resident header) + length in bytes of the name
921                 NewRecord->NonResident.MappingPairsOffset = 0x40 + (AttrContext->pRecord->NameLength * sizeof(WCHAR));
922 
923                 // update the end of the file record
924                 // calculate position of end markers (1 byte for empty data run)
925                 EndAttributeOffset = AttrOffset + NewRecord->NonResident.MappingPairsOffset + 1;
926                 EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, ATTR_RECORD_ALIGNMENT);
927 
928                 // Update the length
929                 NewRecord->Length = EndAttributeOffset - AttrOffset;
930 
931                 ASSERT(NewRecord->Length == NewRecordLength);
932 
933                 // Copy the new attribute record into the file record
934                 RtlCopyMemory(Destination, NewRecord, NewRecord->Length);
935 
936                 // Update the file record end
937                 SetFileRecordEnd(FileRecord,
938                                  (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset),
939                                  FILE_RECORD_END);
940 
941                 // Initialize the MCB, potentially catch an exception
942                 _SEH2_TRY
943                 {
944                     FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
945                 }
946                 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
947                 {
948                     DPRINT1("Unable to create LargeMcb!\n");
949                     if (AttribDataSize.QuadPart > 0)
950                         ExFreePoolWithTag(AttribData, TAG_NTFS);
951                     ExFreePoolWithTag(NewRecord, TAG_NTFS);
952                     _SEH2_YIELD(return _SEH2_GetExceptionCode());
953                 } _SEH2_END;
954 
955                 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized)
956                 NewRecord->IsNonResident = Destination->IsNonResident = 1;
957 
958                 // Update file record on disk
959                 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
960                 if (!NT_SUCCESS(Status))
961                 {
962                     DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
963                     if (AttribDataSize.QuadPart > 0)
964                         ExFreePoolWithTag(AttribData, TAG_NTFS);
965                     ExFreePoolWithTag(NewRecord, TAG_NTFS);
966                     return Status;
967                 }
968 
969                 // Now we need to free the old copy of the attribute record in the context and replace it with the new one
970                 ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
971                 AttrContext->pRecord = NewRecord;
972 
973                 // Now we can treat the attribute as non-resident and enlarge it normally
974                 Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize);
975                 if (!NT_SUCCESS(Status))
976                 {
977                     DPRINT1("ERROR: Unable to migrate resident attribute!\n");
978                     if (AttribDataSize.QuadPart > 0)
979                         ExFreePoolWithTag(AttribData, TAG_NTFS);
980                     return Status;
981                 }
982 
983                 // restore the back-up attribute, if we made one
984                 if (AttribDataSize.QuadPart > 0)
985                 {
986                     Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten, FileRecord);
987                     if (!NT_SUCCESS(Status))
988                     {
989                         DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
990                         // TODO: Reverse migration so no data is lost
991                         ExFreePoolWithTag(AttribData, TAG_NTFS);
992                         return Status;
993                     }
994 
995                     ExFreePoolWithTag(AttribData, TAG_NTFS);
996                 }
997             }
998         }
999     }
1000 
1001     // set the new length of the resident attribute (if we didn't migrate it)
1002     if (!AttrContext->pRecord->IsNonResident)
1003         return InternalSetResidentAttributeLength(Vcb, AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
1004 
1005     return STATUS_SUCCESS;
1006 }
1007 
1008 ULONG
1009 ReadAttribute(PDEVICE_EXTENSION Vcb,
1010               PNTFS_ATTR_CONTEXT Context,
1011               ULONGLONG Offset,
1012               PCHAR Buffer,
1013               ULONG Length)
1014 {
1015     ULONGLONG LastLCN;
1016     PUCHAR DataRun;
1017     LONGLONG DataRunOffset;
1018     ULONGLONG DataRunLength;
1019     LONGLONG DataRunStartLCN;
1020     ULONGLONG CurrentOffset;
1021     ULONG ReadLength;
1022     ULONG AlreadyRead;
1023     NTSTATUS Status;
1024 
1025     //TEMPTEMP
1026     PUCHAR TempBuffer;
1027 
1028     if (!Context->pRecord->IsNonResident)
1029     {
1030         // We need to truncate Offset to a ULONG for pointer arithmetic
1031         // The check below should ensure that Offset is well within the range of 32 bits
1032         ULONG LittleOffset = (ULONG)Offset;
1033 
1034         // Ensure that offset isn't beyond the end of the attribute
1035         if (Offset > Context->pRecord->Resident.ValueLength)
1036             return 0;
1037         if (Offset + Length > Context->pRecord->Resident.ValueLength)
1038             Length = (ULONG)(Context->pRecord->Resident.ValueLength - Offset);
1039 
1040         RtlCopyMemory(Buffer, (PVOID)((ULONG_PTR)Context->pRecord + Context->pRecord->Resident.ValueOffset + LittleOffset), Length);
1041         return Length;
1042     }
1043 
1044     /*
1045      * Non-resident attribute
1046      */
1047 
1048     /*
1049      * I. Find the corresponding start data run.
1050      */
1051 
1052     AlreadyRead = 0;
1053 
1054     // FIXME: Cache seems to be non-working. Disable it for now
1055     //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1056     if (0)
1057     {
1058         DataRun = Context->CacheRun;
1059         LastLCN = Context->CacheRunLastLCN;
1060         DataRunStartLCN = Context->CacheRunStartLCN;
1061         DataRunLength = Context->CacheRunLength;
1062         CurrentOffset = Context->CacheRunCurrentOffset;
1063     }
1064     else
1065     {
1066         //TEMPTEMP
1067         ULONG UsedBufferSize;
1068         TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1069         if (TempBuffer == NULL)
1070         {
1071             return STATUS_INSUFFICIENT_RESOURCES;
1072         }
1073 
1074         LastLCN = 0;
1075         CurrentOffset = 0;
1076 
1077         // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1078         ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
1079                                   TempBuffer,
1080                                   Vcb->NtfsInfo.BytesPerFileRecord,
1081                                   &UsedBufferSize);
1082 
1083         DataRun = TempBuffer;
1084 
1085         while (1)
1086         {
1087             DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1088             if (DataRunOffset != -1)
1089             {
1090                 /* Normal data run. */
1091                 DataRunStartLCN = LastLCN + DataRunOffset;
1092                 LastLCN = DataRunStartLCN;
1093             }
1094             else
1095             {
1096                 /* Sparse data run. */
1097                 DataRunStartLCN = -1;
1098             }
1099 
1100             if (Offset >= CurrentOffset &&
1101                 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
1102             {
1103                 break;
1104             }
1105 
1106             if (*DataRun == 0)
1107             {
1108                 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1109                 return AlreadyRead;
1110             }
1111 
1112             CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1113         }
1114     }
1115 
1116     /*
1117      * II. Go through the run list and read the data
1118      */
1119 
1120     ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
1121     if (DataRunStartLCN == -1)
1122     {
1123         RtlZeroMemory(Buffer, ReadLength);
1124         Status = STATUS_SUCCESS;
1125     }
1126     else
1127     {
1128         Status = NtfsReadDisk(Vcb->StorageDevice,
1129                               DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
1130                               ReadLength,
1131                               Vcb->NtfsInfo.BytesPerSector,
1132                               (PVOID)Buffer,
1133                               FALSE);
1134     }
1135     if (NT_SUCCESS(Status))
1136     {
1137         Length -= ReadLength;
1138         Buffer += ReadLength;
1139         AlreadyRead += ReadLength;
1140 
1141         if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
1142         {
1143             CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1144             DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1145             if (DataRunOffset != (ULONGLONG)-1)
1146             {
1147                 DataRunStartLCN = LastLCN + DataRunOffset;
1148                 LastLCN = DataRunStartLCN;
1149             }
1150             else
1151                 DataRunStartLCN = -1;
1152         }
1153 
1154         while (Length > 0)
1155         {
1156             ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
1157             if (DataRunStartLCN == -1)
1158                 RtlZeroMemory(Buffer, ReadLength);
1159             else
1160             {
1161                 Status = NtfsReadDisk(Vcb->StorageDevice,
1162                                       DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
1163                                       ReadLength,
1164                                       Vcb->NtfsInfo.BytesPerSector,
1165                                       (PVOID)Buffer,
1166                                       FALSE);
1167                 if (!NT_SUCCESS(Status))
1168                     break;
1169             }
1170 
1171             Length -= ReadLength;
1172             Buffer += ReadLength;
1173             AlreadyRead += ReadLength;
1174 
1175             /* We finished this request, but there still data in this data run. */
1176             if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1177                 break;
1178 
1179             /*
1180              * Go to next run in the list.
1181              */
1182 
1183             if (*DataRun == 0)
1184                 break;
1185             CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1186             DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1187             if (DataRunOffset != -1)
1188             {
1189                 /* Normal data run. */
1190                 DataRunStartLCN = LastLCN + DataRunOffset;
1191                 LastLCN = DataRunStartLCN;
1192             }
1193             else
1194             {
1195                 /* Sparse data run. */
1196                 DataRunStartLCN = -1;
1197             }
1198         } /* while */
1199 
1200     } /* if Disk */
1201 
1202     // TEMPTEMP
1203     if (Context->pRecord->IsNonResident)
1204         ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1205 
1206     Context->CacheRun = DataRun;
1207     Context->CacheRunOffset = Offset + AlreadyRead;
1208     Context->CacheRunStartLCN = DataRunStartLCN;
1209     Context->CacheRunLength = DataRunLength;
1210     Context->CacheRunLastLCN = LastLCN;
1211     Context->CacheRunCurrentOffset = CurrentOffset;
1212 
1213     return AlreadyRead;
1214 }
1215 
1216 
1217 /**
1218 * @name WriteAttribute
1219 * @implemented
1220 *
1221 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1222 * and it still needs more documentation / cleaning up.
1223 *
1224 * @param Vcb
1225 * Volume Control Block indicating which volume to write the attribute to
1226 *
1227 * @param Context
1228 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1229 *
1230 * @param Offset
1231 * Offset, in bytes, from the beginning of the attribute indicating where to start
1232 * writing data
1233 *
1234 * @param Buffer
1235 * The data that's being written to the device
1236 *
1237 * @param Length
1238 * How much data will be written, in bytes
1239 *
1240 * @param RealLengthWritten
1241 * Pointer to a ULONG which will receive how much data was written, in bytes
1242 *
1243 * @param FileRecord
1244 * Optional pointer to a FILE_RECORD_HEADER that contains a copy of the file record
1245 * being written to. Can be NULL, in which case the file record will be read from disk.
1246 * If not-null, WriteAttribute() will skip reading from disk, and FileRecord
1247 * will be updated with the newly-written attribute before the function returns.
1248 *
1249 * @return
1250 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1251 * writing to a sparse file.
1252 *
1253 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1254 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1255 *
1256 */
1257 
1258 NTSTATUS
1259 WriteAttribute(PDEVICE_EXTENSION Vcb,
1260                PNTFS_ATTR_CONTEXT Context,
1261                ULONGLONG Offset,
1262                const PUCHAR Buffer,
1263                ULONG Length,
1264                PULONG RealLengthWritten,
1265                PFILE_RECORD_HEADER FileRecord)
1266 {
1267     ULONGLONG LastLCN;
1268     PUCHAR DataRun;
1269     LONGLONG DataRunOffset;
1270     ULONGLONG DataRunLength;
1271     LONGLONG DataRunStartLCN;
1272     ULONGLONG CurrentOffset;
1273     ULONG WriteLength;
1274     NTSTATUS Status;
1275     PUCHAR SourceBuffer = Buffer;
1276     LONGLONG StartingOffset;
1277     BOOLEAN FileRecordAllocated = FALSE;
1278 
1279     //TEMPTEMP
1280     PUCHAR TempBuffer;
1281 
1282 
1283     DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten, FileRecord);
1284 
1285     *RealLengthWritten = 0;
1286 
1287     // is this a resident attribute?
1288     if (!Context->pRecord->IsNonResident)
1289     {
1290         ULONG AttributeOffset;
1291         PNTFS_ATTR_CONTEXT FoundContext;
1292         PNTFS_ATTR_RECORD Destination;
1293 
1294         // Ensure requested data is within the bounds of the attribute
1295         ASSERT(Offset + Length <= Context->pRecord->Resident.ValueLength);
1296 
1297         if (Offset + Length > Context->pRecord->Resident.ValueLength)
1298         {
1299             DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1300             return STATUS_INVALID_PARAMETER;
1301         }
1302 
1303         // Do we need to read the file record?
1304         if (FileRecord == NULL)
1305         {
1306             FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
1307             if (!FileRecord)
1308             {
1309                 DPRINT1("Error: Couldn't allocate file record!\n");
1310                 return STATUS_NO_MEMORY;
1311             }
1312 
1313             FileRecordAllocated = TRUE;
1314 
1315             // read the file record
1316             ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1317         }
1318 
1319         // find where to write the attribute data to
1320         Status = FindAttribute(Vcb, FileRecord,
1321                                Context->pRecord->Type,
1322                                (PCWSTR)((ULONG_PTR)Context->pRecord + Context->pRecord->NameOffset),
1323                                Context->pRecord->NameLength,
1324                                &FoundContext,
1325                                &AttributeOffset);
1326 
1327         if (!NT_SUCCESS(Status))
1328         {
1329             DPRINT1("ERROR: Couldn't find matching attribute!\n");
1330             if(FileRecordAllocated)
1331                 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord);
1332             return Status;
1333         }
1334 
1335         Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttributeOffset);
1336 
1337         DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->pRecord->Resident.ValueLength);
1338 
1339         // Will we be writing past the end of the allocated file record?
1340         if (Offset + Length + AttributeOffset + Context->pRecord->Resident.ValueOffset > Vcb->NtfsInfo.BytesPerFileRecord)
1341         {
1342             DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1343             ReleaseAttributeContext(FoundContext);
1344             if (FileRecordAllocated)
1345                 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord);
1346             return STATUS_INVALID_PARAMETER;
1347         }
1348 
1349         // copy the data being written into the file record. We cast Offset to ULONG, which is safe because it's range has been verified.
1350         RtlCopyMemory((PCHAR)((ULONG_PTR)Destination + Context->pRecord->Resident.ValueOffset + (ULONG)Offset), Buffer, Length);
1351 
1352         Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1353 
1354         // Update the context's copy of the resident attribute
1355         ASSERT(Context->pRecord->Length == Destination->Length);
1356         RtlCopyMemory((PVOID)Context->pRecord, Destination, Context->pRecord->Length);
1357 
1358         ReleaseAttributeContext(FoundContext);
1359         if (FileRecordAllocated)
1360             ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord);
1361 
1362         if (NT_SUCCESS(Status))
1363             *RealLengthWritten = Length;
1364 
1365         return Status;
1366     }
1367 
1368     // This is a non-resident attribute.
1369 
1370     // I. Find the corresponding start data run.
1371 
1372     // FIXME: Cache seems to be non-working. Disable it for now
1373     //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1374     /*if (0)
1375     {
1376     DataRun = Context->CacheRun;
1377     LastLCN = Context->CacheRunLastLCN;
1378     DataRunStartLCN = Context->CacheRunStartLCN;
1379     DataRunLength = Context->CacheRunLength;
1380     CurrentOffset = Context->CacheRunCurrentOffset;
1381     }
1382     else*/
1383     {
1384         ULONG UsedBufferSize;
1385         LastLCN = 0;
1386         CurrentOffset = 0;
1387 
1388         // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1389         TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1390         if (TempBuffer == NULL)
1391         {
1392             return STATUS_INSUFFICIENT_RESOURCES;
1393         }
1394 
1395         ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
1396                                   TempBuffer,
1397                                   Vcb->NtfsInfo.BytesPerFileRecord,
1398                                   &UsedBufferSize);
1399 
1400         DataRun = TempBuffer;
1401 
1402         while (1)
1403         {
1404             DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1405             if (DataRunOffset != -1)
1406             {
1407                 // Normal data run.
1408                 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1409                 DataRunStartLCN = LastLCN + DataRunOffset;
1410                 LastLCN = DataRunStartLCN;
1411             }
1412             else
1413             {
1414                 // Sparse data run. We can't support writing to sparse files yet
1415                 // (it may require increasing the allocation size).
1416                 DataRunStartLCN = -1;
1417                 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1418                 Status = STATUS_NOT_IMPLEMENTED;
1419                 goto Cleanup;
1420             }
1421 
1422             // Have we reached the data run we're trying to write to?
1423             if (Offset >= CurrentOffset &&
1424                 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
1425             {
1426                 break;
1427             }
1428 
1429             if (*DataRun == 0)
1430             {
1431                 // We reached the last assigned cluster
1432                 // TODO: assign new clusters to the end of the file.
1433                 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1434                 // [We can reach here by creating a new file record when the MFT isn't large enough]
1435                 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1436                 Status = STATUS_END_OF_FILE;
1437                 goto Cleanup;
1438             }
1439 
1440             CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1441         }
1442     }
1443 
1444     // II. Go through the run list and write the data
1445 
1446     /* REVIEWME -- As adapted from NtfsReadAttribute():
1447     We seem to be making a special case for the first applicable data run, but I'm not sure why.
1448     Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1449 
1450     WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
1451 
1452     StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
1453 
1454     // Write the data to the disk
1455     Status = NtfsWriteDisk(Vcb->StorageDevice,
1456                            StartingOffset,
1457                            WriteLength,
1458                            Vcb->NtfsInfo.BytesPerSector,
1459                            (PVOID)SourceBuffer);
1460 
1461     // Did the write fail?
1462     if (!NT_SUCCESS(Status))
1463     {
1464         Context->CacheRun = DataRun;
1465         Context->CacheRunOffset = Offset;
1466         Context->CacheRunStartLCN = DataRunStartLCN;
1467         Context->CacheRunLength = DataRunLength;
1468         Context->CacheRunLastLCN = LastLCN;
1469         Context->CacheRunCurrentOffset = CurrentOffset;
1470 
1471         goto Cleanup;
1472     }
1473 
1474     Length -= WriteLength;
1475     SourceBuffer += WriteLength;
1476     *RealLengthWritten += WriteLength;
1477 
1478     // Did we write to the end of the data run?
1479     if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
1480     {
1481         // Advance to the next data run
1482         CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1483         DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1484 
1485         if (DataRunOffset != (ULONGLONG)-1)
1486         {
1487             DataRunStartLCN = LastLCN + DataRunOffset;
1488             LastLCN = DataRunStartLCN;
1489         }
1490         else
1491             DataRunStartLCN = -1;
1492     }
1493 
1494     // Do we have more data to write?
1495     while (Length > 0)
1496     {
1497         // Make sure we don't write past the end of the current data run
1498         WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
1499 
1500         // Are we dealing with a sparse data run?
1501         if (DataRunStartLCN == -1)
1502         {
1503             DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1504             Status = STATUS_NOT_IMPLEMENTED;
1505             goto Cleanup;
1506         }
1507         else
1508         {
1509             // write the data to the disk
1510             Status = NtfsWriteDisk(Vcb->StorageDevice,
1511                                    DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
1512                                    WriteLength,
1513                                    Vcb->NtfsInfo.BytesPerSector,
1514                                    (PVOID)SourceBuffer);
1515             if (!NT_SUCCESS(Status))
1516                 break;
1517         }
1518 
1519         Length -= WriteLength;
1520         SourceBuffer += WriteLength;
1521         *RealLengthWritten += WriteLength;
1522 
1523         // We finished this request, but there's still data in this data run.
1524         if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1525             break;
1526 
1527         // Go to next run in the list.
1528 
1529         if (*DataRun == 0)
1530         {
1531             // that was the last run
1532             if (Length > 0)
1533             {
1534                 // Failed sanity check.
1535                 DPRINT1("Encountered EOF before expected!\n");
1536                 Status = STATUS_END_OF_FILE;
1537                 goto Cleanup;
1538             }
1539 
1540             break;
1541         }
1542 
1543         // Advance to the next data run
1544         CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1545         DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1546         if (DataRunOffset != -1)
1547         {
1548             // Normal data run.
1549             DataRunStartLCN = LastLCN + DataRunOffset;
1550             LastLCN = DataRunStartLCN;
1551         }
1552         else
1553         {
1554             // Sparse data run.
1555             DataRunStartLCN = -1;
1556         }
1557     } // end while (Length > 0) [more data to write]
1558 
1559     Context->CacheRun = DataRun;
1560     Context->CacheRunOffset = Offset + *RealLengthWritten;
1561     Context->CacheRunStartLCN = DataRunStartLCN;
1562     Context->CacheRunLength = DataRunLength;
1563     Context->CacheRunLastLCN = LastLCN;
1564     Context->CacheRunCurrentOffset = CurrentOffset;
1565 
1566 Cleanup:
1567     // TEMPTEMP
1568     if (Context->pRecord->IsNonResident)
1569         ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1570 
1571     return Status;
1572 }
1573 
1574 NTSTATUS
1575 ReadFileRecord(PDEVICE_EXTENSION Vcb,
1576                ULONGLONG index,
1577                PFILE_RECORD_HEADER file)
1578 {
1579     ULONGLONG BytesRead;
1580 
1581     DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
1582 
1583     BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
1584     if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
1585     {
1586         DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
1587         return STATUS_PARTIAL_COPY;
1588     }
1589 
1590     /* Apply update sequence array fixups. */
1591     DPRINT("Sequence number: %u\n", file->SequenceNumber);
1592     return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
1593 }
1594 
1595 
1596 /**
1597 * Searches a file's parent directory (given the parent's index in the mft)
1598 * for the given file. Upon finding an index entry for that file, updates
1599 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1600 *
1601 * (Most of this code was copied from NtfsFindMftRecord)
1602 */
1603 NTSTATUS
1604 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
1605                      ULONGLONG ParentMFTIndex,
1606                      PUNICODE_STRING FileName,
1607                      BOOLEAN DirSearch,
1608                      ULONGLONG NewDataSize,
1609                      ULONGLONG NewAllocationSize,
1610                      BOOLEAN CaseSensitive)
1611 {
1612     PFILE_RECORD_HEADER MftRecord;
1613     PNTFS_ATTR_CONTEXT IndexRootCtx;
1614     PINDEX_ROOT_ATTRIBUTE IndexRoot;
1615     PCHAR IndexRecord;
1616     PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1617     NTSTATUS Status;
1618     ULONG CurrentEntry = 0;
1619 
1620     DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n",
1621            Vcb,
1622            ParentMFTIndex,
1623            FileName,
1624            DirSearch ? "TRUE" : "FALSE",
1625            NewDataSize,
1626            NewAllocationSize,
1627            CaseSensitive ? "TRUE" : "FALSE");
1628 
1629     MftRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
1630     if (MftRecord == NULL)
1631     {
1632         return STATUS_INSUFFICIENT_RESOURCES;
1633     }
1634 
1635     Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
1636     if (!NT_SUCCESS(Status))
1637     {
1638         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1639         return Status;
1640     }
1641 
1642     ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1643     Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1644     if (!NT_SUCCESS(Status))
1645     {
1646         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1647         return Status;
1648     }
1649 
1650     IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1651     if (IndexRecord == NULL)
1652     {
1653         ReleaseAttributeContext(IndexRootCtx);
1654         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1655         return STATUS_INSUFFICIENT_RESOURCES;
1656     }
1657 
1658     Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(IndexRootCtx->pRecord));
1659     if (!NT_SUCCESS(Status))
1660     {
1661         DPRINT1("ERROR: Failed to read Index Root!\n");
1662         ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1663         ReleaseAttributeContext(IndexRootCtx);
1664         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1665         return Status;
1666     }
1667 
1668     IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1669     IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1670     // Index root is always resident.
1671     IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1672 
1673     DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1674 
1675     Status = UpdateIndexEntryFileNameSize(Vcb,
1676                                           MftRecord,
1677                                           IndexRecord,
1678                                           IndexRoot->SizeOfEntry,
1679                                           IndexEntry,
1680                                           IndexEntryEnd,
1681                                           FileName,
1682                                           &CurrentEntry,
1683                                           &CurrentEntry,
1684                                           DirSearch,
1685                                           NewDataSize,
1686                                           NewAllocationSize,
1687                                           CaseSensitive);
1688 
1689     if (Status == STATUS_PENDING)
1690     {
1691         // we need to write the index root attribute back to disk
1692         ULONG LengthWritten;
1693         Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten, MftRecord);
1694         if (!NT_SUCCESS(Status))
1695         {
1696             DPRINT1("ERROR: Couldn't update Index Root!\n");
1697         }
1698 
1699     }
1700 
1701     ReleaseAttributeContext(IndexRootCtx);
1702     ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1703     ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1704 
1705     return Status;
1706 }
1707 
1708 /**
1709 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1710 * proper index entry.
1711 * (Heavily based on BrowseIndexEntries)
1712 */
1713 NTSTATUS
1714 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
1715                              PFILE_RECORD_HEADER MftRecord,
1716                              PCHAR IndexRecord,
1717                              ULONG IndexBlockSize,
1718                              PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1719                              PINDEX_ENTRY_ATTRIBUTE LastEntry,
1720                              PUNICODE_STRING FileName,
1721                              PULONG StartEntry,
1722                              PULONG CurrentEntry,
1723                              BOOLEAN DirSearch,
1724                              ULONGLONG NewDataSize,
1725                              ULONGLONG NewAllocatedSize,
1726                              BOOLEAN CaseSensitive)
1727 {
1728     NTSTATUS Status;
1729     ULONG RecordOffset;
1730     PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1731     PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1732     ULONGLONG IndexAllocationSize;
1733     PINDEX_BUFFER IndexBuffer;
1734 
1735     DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n",
1736            Vcb,
1737            MftRecord,
1738            IndexRecord,
1739            IndexBlockSize,
1740            FirstEntry,
1741            LastEntry,
1742            FileName,
1743            *StartEntry,
1744            *CurrentEntry,
1745            DirSearch ? "TRUE" : "FALSE",
1746            NewDataSize,
1747            NewAllocatedSize,
1748            CaseSensitive ? "TRUE" : "FALSE");
1749 
1750     // find the index entry responsible for the file we're trying to update
1751     IndexEntry = FirstEntry;
1752     while (IndexEntry < LastEntry &&
1753            !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1754     {
1755         if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > NTFS_FILE_FIRST_USER_FILE &&
1756             *CurrentEntry >= *StartEntry &&
1757             IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1758             CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
1759         {
1760             *StartEntry = *CurrentEntry;
1761             IndexEntry->FileName.DataSize = NewDataSize;
1762             IndexEntry->FileName.AllocatedSize = NewAllocatedSize;
1763             // indicate that the caller will still need to write the structure to the disk
1764             return STATUS_PENDING;
1765         }
1766 
1767         (*CurrentEntry) += 1;
1768         ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1769         IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1770     }
1771 
1772     /* If we're already browsing a subnode */
1773     if (IndexRecord == NULL)
1774     {
1775         return STATUS_OBJECT_PATH_NOT_FOUND;
1776     }
1777 
1778     /* If there's no subnode */
1779     if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1780     {
1781         return STATUS_OBJECT_PATH_NOT_FOUND;
1782     }
1783 
1784     Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1785     if (!NT_SUCCESS(Status))
1786     {
1787         DPRINT("Corrupted filesystem!\n");
1788         return Status;
1789     }
1790 
1791     IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord);
1792     Status = STATUS_OBJECT_PATH_NOT_FOUND;
1793     for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1794     {
1795         ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1796         Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1797         if (!NT_SUCCESS(Status))
1798         {
1799             break;
1800         }
1801 
1802         IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1803         ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1804         ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1805         FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1806         LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1807         ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1808 
1809         Status = UpdateIndexEntryFileNameSize(NULL,
1810                                               NULL,
1811                                               NULL,
1812                                               0,
1813                                               FirstEntry,
1814                                               LastEntry,
1815                                               FileName,
1816                                               StartEntry,
1817                                               CurrentEntry,
1818                                               DirSearch,
1819                                               NewDataSize,
1820                                               NewAllocatedSize,
1821                                               CaseSensitive);
1822         if (Status == STATUS_PENDING)
1823         {
1824             // write the index record back to disk
1825             ULONG Written;
1826 
1827             // first we need to update the fixup values for the index block
1828             Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1829             if (!NT_SUCCESS(Status))
1830             {
1831                 DPRINT1("Error: Failed to update fixup sequence array!\n");
1832                 break;
1833             }
1834 
1835             Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written, MftRecord);
1836             if (!NT_SUCCESS(Status))
1837             {
1838                 DPRINT1("ERROR Performing write!\n");
1839                 break;
1840             }
1841 
1842             Status = STATUS_SUCCESS;
1843             break;
1844         }
1845         if (NT_SUCCESS(Status))
1846         {
1847             break;
1848         }
1849     }
1850 
1851     ReleaseAttributeContext(IndexAllocationCtx);
1852     return Status;
1853 }
1854 
1855 /**
1856 * @name UpdateFileRecord
1857 * @implemented
1858 *
1859 * Writes a file record to the master file table, at a given index.
1860 *
1861 * @param Vcb
1862 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1863 *
1864 * @param MftIndex
1865 * Target index in the master file table to store the file record.
1866 *
1867 * @param FileRecord
1868 * Pointer to the complete file record which will be written to the master file table.
1869 *
1870 * @return
1871 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1872 *
1873 */
1874 NTSTATUS
1875 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
1876                  ULONGLONG MftIndex,
1877                  PFILE_RECORD_HEADER FileRecord)
1878 {
1879     ULONG BytesWritten;
1880     NTSTATUS Status = STATUS_SUCCESS;
1881 
1882     DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord);
1883 
1884     // Add the fixup array to prepare the data for writing to disk
1885     AddFixupArray(Vcb, &FileRecord->Ntfs);
1886 
1887     // write the file record to the master file table
1888     Status = WriteAttribute(Vcb,
1889                             Vcb->MFTContext,
1890                             MftIndex * Vcb->NtfsInfo.BytesPerFileRecord,
1891                             (const PUCHAR)FileRecord,
1892                             Vcb->NtfsInfo.BytesPerFileRecord,
1893                             &BytesWritten,
1894                             FileRecord);
1895 
1896     if (!NT_SUCCESS(Status))
1897     {
1898         DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
1899     }
1900 
1901     // remove the fixup array (so the file record pointer can still be used)
1902     FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs);
1903 
1904     return Status;
1905 }
1906 
1907 
1908 NTSTATUS
1909 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
1910                          PNTFS_RECORD_HEADER Record)
1911 {
1912     USHORT *USA;
1913     USHORT USANumber;
1914     USHORT USACount;
1915     USHORT *Block;
1916 
1917     USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
1918     USANumber = *(USA++);
1919     USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
1920     Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
1921 
1922     DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
1923 
1924     while (USACount)
1925     {
1926         if (*Block != USANumber)
1927         {
1928             DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
1929             return STATUS_UNSUCCESSFUL;
1930         }
1931         *Block = *(USA++);
1932         Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
1933         USACount--;
1934     }
1935 
1936     return STATUS_SUCCESS;
1937 }
1938 
1939 /**
1940 * @name AddNewMftEntry
1941 * @implemented
1942 *
1943 * Adds a file record to the master file table of a given device.
1944 *
1945 * @param FileRecord
1946 * Pointer to a complete file record which will be saved to disk.
1947 *
1948 * @param DeviceExt
1949 * Pointer to the DEVICE_EXTENSION of the target drive.
1950 *
1951 * @param DestinationIndex
1952 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1953 *
1954 * @param CanWait
1955 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
1956 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
1957 *
1958 * @return
1959 * STATUS_SUCCESS on success.
1960 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1961 * to read the attribute.
1962 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1963 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
1964 */
1965 NTSTATUS
1966 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
1967                PDEVICE_EXTENSION DeviceExt,
1968                PULONGLONG DestinationIndex,
1969                BOOLEAN CanWait)
1970 {
1971     NTSTATUS Status = STATUS_SUCCESS;
1972     ULONGLONG MftIndex;
1973     RTL_BITMAP Bitmap;
1974     ULONGLONG BitmapDataSize;
1975     ULONGLONG AttrBytesRead;
1976     PUCHAR BitmapData;
1977     PUCHAR BitmapBuffer;
1978     ULONG LengthWritten;
1979     PNTFS_ATTR_CONTEXT BitmapContext;
1980     LARGE_INTEGER BitmapBits;
1981     UCHAR SystemReservedBits;
1982 
1983     DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
1984 
1985     // First, we have to read the mft's $Bitmap attribute
1986 
1987     // Find the attribute
1988     Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
1989     if (!NT_SUCCESS(Status))
1990     {
1991         DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1992         return Status;
1993     }
1994 
1995     // Get size of bitmap
1996     BitmapDataSize = AttributeDataLength(BitmapContext->pRecord);
1997 
1998     // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple
1999     // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer
2000     BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS);
2001     if (!BitmapBuffer)
2002     {
2003         ReleaseAttributeContext(BitmapContext);
2004         return STATUS_INSUFFICIENT_RESOURCES;
2005     }
2006     RtlZeroMemory(BitmapBuffer, BitmapDataSize + sizeof(ULONG));
2007 
2008     // Get a ULONG-aligned pointer for the bitmap itself
2009     BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG));
2010 
2011     // read $Bitmap attribute
2012     AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize);
2013 
2014     if (AttrBytesRead != BitmapDataSize)
2015     {
2016         DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
2017         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2018         ReleaseAttributeContext(BitmapContext);
2019         return STATUS_OBJECT_NAME_NOT_FOUND;
2020     }
2021 
2022     // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records
2023     // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk).
2024     SystemReservedBits = BitmapData[2];
2025     BitmapData[2] = 0xff;
2026 
2027     // Calculate bit count
2028     BitmapBits.QuadPart = AttributeDataLength(DeviceExt->MFTContext->pRecord) /
2029                           DeviceExt->NtfsInfo.BytesPerFileRecord;
2030     if (BitmapBits.HighPart != 0)
2031     {
2032         DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
2033         NtfsGlobalData->EnableWriteSupport = FALSE;
2034         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2035         ReleaseAttributeContext(BitmapContext);
2036         return STATUS_NOT_IMPLEMENTED;
2037     }
2038 
2039     // convert buffer into bitmap
2040     RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart);
2041 
2042     // set next available bit, preferrably after 23rd bit
2043     MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
2044     if ((LONG)MftIndex == -1)
2045     {
2046         DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
2047 
2048         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2049         ReleaseAttributeContext(BitmapContext);
2050 
2051         // Couldn't find a free record in the MFT, add some blank records and try again
2052         Status = IncreaseMftSize(DeviceExt, CanWait);
2053         if (!NT_SUCCESS(Status))
2054         {
2055             DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
2056             return Status;
2057         }
2058 
2059         return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait);
2060     }
2061 
2062     DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
2063 
2064     // update file record with index
2065     FileRecord->MFTRecordNumber = MftIndex;
2066 
2067     // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
2068 
2069     // Restore the system reserved bits
2070     BitmapData[2] = SystemReservedBits;
2071 
2072     // write the bitmap back to the MFT's $Bitmap attribute
2073     Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord);
2074     if (!NT_SUCCESS(Status))
2075     {
2076         DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
2077         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2078         ReleaseAttributeContext(BitmapContext);
2079         return Status;
2080     }
2081 
2082     // update the file record (write it to disk)
2083     Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
2084 
2085     if (!NT_SUCCESS(Status))
2086     {
2087         DPRINT1("ERROR: Unable to write file record!\n");
2088         ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2089         ReleaseAttributeContext(BitmapContext);
2090         return Status;
2091     }
2092 
2093     *DestinationIndex = MftIndex;
2094 
2095     ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2096     ReleaseAttributeContext(BitmapContext);
2097 
2098     return Status;
2099 }
2100 
2101 /**
2102 * @name NtfsAddFilenameToDirectory
2103 * @implemented
2104 *
2105 * Adds a $FILE_NAME attribute to a given directory index.
2106 *
2107 * @param DeviceExt
2108 * Points to the target disk's DEVICE_EXTENSION.
2109 *
2110 * @param DirectoryMftIndex
2111 * Mft index of the parent directory which will receive the file.
2112 *
2113 * @param FileReferenceNumber
2114 * File reference of the file to be added to the directory. This is a combination of the
2115 * Mft index and sequence number.
2116 *
2117 * @param FilenameAttribute
2118 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
2119 *
2120 * @param CaseSensitive
2121 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
2122 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
2123 *
2124 * @return
2125 * STATUS_SUCCESS on success.
2126 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
2127 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
2128 *
2129 * @remarks
2130 * WIP - Can only support a few files in a directory.
2131 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
2132 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
2133 * get both attributes added to its parent directory.
2134 */
2135 NTSTATUS
2136 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
2137                            ULONGLONG DirectoryMftIndex,
2138                            ULONGLONG FileReferenceNumber,
2139                            PFILENAME_ATTRIBUTE FilenameAttribute,
2140                            BOOLEAN CaseSensitive)
2141 {
2142     NTSTATUS Status = STATUS_SUCCESS;
2143     PFILE_RECORD_HEADER ParentFileRecord;
2144     PNTFS_ATTR_CONTEXT IndexRootContext;
2145     PINDEX_ROOT_ATTRIBUTE I30IndexRoot;
2146     ULONG IndexRootOffset;
2147     ULONGLONG I30IndexRootLength;
2148     ULONG LengthWritten;
2149     PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
2150     ULONG AttributeLength;
2151     PNTFS_ATTR_RECORD NextAttribute;
2152     PB_TREE NewTree;
2153     ULONG BtreeIndexLength;
2154     ULONG MaxIndexRootSize;
2155     PB_TREE_KEY NewLeftKey;
2156     PB_TREE_FILENAME_NODE NewRightHandNode;
2157     LARGE_INTEGER MinIndexRootSize;
2158     ULONG NewMaxIndexRootSize;
2159     ULONG NodeSize;
2160 
2161     // Allocate memory for the parent directory
2162     ParentFileRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
2163     if (!ParentFileRecord)
2164     {
2165         DPRINT1("ERROR: Couldn't allocate memory for file record!\n");
2166         return STATUS_INSUFFICIENT_RESOURCES;
2167     }
2168 
2169     // Open the parent directory
2170     Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2171     if (!NT_SUCCESS(Status))
2172     {
2173         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2174         DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n",
2175                 DirectoryMftIndex);
2176         return Status;
2177     }
2178 
2179 #ifndef NDEBUG
2180     DPRINT1("Dumping old parent file record:\n");
2181     NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
2182 #endif
2183 
2184     // Find the index root attribute for the directory
2185     Status = FindAttribute(DeviceExt,
2186                            ParentFileRecord,
2187                            AttributeIndexRoot,
2188                            L"$I30",
2189                            4,
2190                            &IndexRootContext,
2191                            &IndexRootOffset);
2192     if (!NT_SUCCESS(Status))
2193     {
2194         DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n",
2195                 DirectoryMftIndex);
2196         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2197         return Status;
2198     }
2199 
2200     // Find the maximum index size given what the file record can hold
2201     // First, find the max index size assuming index root is the last attribute
2202     MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord               // Start with the size of a file record
2203                        - IndexRootOffset                                    // Subtract the length of everything that comes before index root
2204                        - IndexRootContext->pRecord->Resident.ValueOffset    // Subtract the length of the attribute header for index root
2205                        - sizeof(INDEX_ROOT_ATTRIBUTE)                       // Subtract the length of the index root header
2206                        - (sizeof(ULONG) * 2);                               // Subtract the length of the file record end marker and padding
2207 
2208     // Are there attributes after this one?
2209     NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
2210     if (NextAttribute->Type != AttributeEnd)
2211     {
2212         // Find the length of all attributes after this one, not counting the end marker
2213         ULONG LengthOfAttributes = 0;
2214         PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
2215         while (CurrentAttribute->Type != AttributeEnd)
2216         {
2217             LengthOfAttributes += CurrentAttribute->Length;
2218             CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
2219         }
2220 
2221         // Leave room for the existing attributes
2222         MaxIndexRootSize -= LengthOfAttributes;
2223     }
2224 
2225     // Allocate memory for the index root data
2226     I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord);
2227     I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS);
2228     if (!I30IndexRoot)
2229     {
2230         DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n");
2231         ReleaseAttributeContext(IndexRootContext);
2232         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2233         return STATUS_INSUFFICIENT_RESOURCES;
2234     }
2235 
2236     // Read the Index Root
2237     Status = ReadAttribute(DeviceExt, IndexRootContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength);
2238     if (!NT_SUCCESS(Status))
2239     {
2240         DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex);
2241         ReleaseAttributeContext(IndexRootContext);
2242         ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2243         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2244         return Status;
2245     }
2246 
2247     // Convert the index to a B*Tree
2248     Status = CreateBTreeFromIndex(DeviceExt,
2249                                   ParentFileRecord,
2250                                   IndexRootContext,
2251                                   I30IndexRoot,
2252                                   &NewTree);
2253     if (!NT_SUCCESS(Status))
2254     {
2255         DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
2256         ReleaseAttributeContext(IndexRootContext);
2257         ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2258         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2259         return Status;
2260     }
2261 
2262 #ifndef NDEBUG
2263     DumpBTree(NewTree);
2264 #endif
2265 
2266     // Insert the key for the file we're adding
2267     Status = NtfsInsertKey(NewTree,
2268                            FileReferenceNumber,
2269                            FilenameAttribute,
2270                            NewTree->RootNode,
2271                            CaseSensitive,
2272                            MaxIndexRootSize,
2273                            I30IndexRoot->SizeOfEntry,
2274                            &NewLeftKey,
2275                            &NewRightHandNode);
2276     if (!NT_SUCCESS(Status))
2277     {
2278         DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
2279         DestroyBTree(NewTree);
2280         ReleaseAttributeContext(IndexRootContext);
2281         ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2282         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2283         return Status;
2284     }
2285 
2286 #ifndef NDEBUG
2287     DumpBTree(NewTree);
2288 #endif
2289 
2290     // The root node can't be split
2291     ASSERT(NewLeftKey == NULL);
2292     ASSERT(NewRightHandNode == NULL);
2293 
2294     // Convert B*Tree back to Index
2295 
2296     // Updating the index allocation can change the size available for the index root,
2297     // And if the index root is demoted, the index allocation will need to be updated again,
2298     // which may change the size available for index root... etc.
2299     // My solution is to decrease index root to the size it would be if it was demoted,
2300     // then UpdateIndexAllocation will have an accurate representation of the maximum space
2301     // it can use in the file record. There's still a chance that the act of allocating an
2302     // index node after demoting the index root will increase the size of the file record beyond
2303     // it's limit, but if that happens, an attribute-list will most definitely be needed.
2304     // This a bit hacky, but it seems to be functional.
2305 
2306     // Calculate the minimum size of the index root attribute, considering one dummy key and one VCN
2307     MinIndexRootSize.QuadPart = sizeof(INDEX_ROOT_ATTRIBUTE) // size of the index root headers
2308                                 + 0x18; // Size of dummy key with a VCN for a subnode
2309     ASSERT(MinIndexRootSize.QuadPart % ATTR_RECORD_ALIGNMENT == 0);
2310 
2311     // Temporarily shrink the index root to it's minimal size
2312     AttributeLength = MinIndexRootSize.LowPart;
2313     AttributeLength += sizeof(INDEX_ROOT_ATTRIBUTE);
2314 
2315 
2316     // FIXME: IndexRoot will probably be invalid until we're finished. If we fail before we finish, the directory will probably be toast.
2317     // The potential for catastrophic data-loss exists!!! :)
2318 
2319     // Update the length of the attribute in the file record of the parent directory
2320     Status = InternalSetResidentAttributeLength(DeviceExt,
2321                                                 IndexRootContext,
2322                                                 ParentFileRecord,
2323                                                 IndexRootOffset,
2324                                                 AttributeLength);
2325     if (!NT_SUCCESS(Status))
2326     {
2327         DPRINT1("ERROR: Unable to set length of index root!\n");
2328         DestroyBTree(NewTree);
2329         ReleaseAttributeContext(IndexRootContext);
2330         ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2331         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2332         return Status;
2333     }
2334 
2335     // Update the index allocation
2336     Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
2337     if (!NT_SUCCESS(Status))
2338     {
2339         DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2340         DestroyBTree(NewTree);
2341         ReleaseAttributeContext(IndexRootContext);
2342         ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2343         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2344         return Status;
2345     }
2346 
2347 #ifndef NDEBUG
2348     DPRINT1("Index Allocation updated\n");
2349     DumpBTree(NewTree);
2350 #endif
2351 
2352     // Find the maximum index root size given what the file record can hold
2353     // First, find the max index size assuming index root is the last attribute
2354     NewMaxIndexRootSize =
2355        DeviceExt->NtfsInfo.BytesPerFileRecord                // Start with the size of a file record
2356         - IndexRootOffset                                    // Subtract the length of everything that comes before index root
2357         - IndexRootContext->pRecord->Resident.ValueOffset    // Subtract the length of the attribute header for index root
2358         - sizeof(INDEX_ROOT_ATTRIBUTE)                       // Subtract the length of the index root header
2359         - (sizeof(ULONG) * 2);                               // Subtract the length of the file record end marker and padding
2360 
2361     // Are there attributes after this one?
2362     NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
2363     if (NextAttribute->Type != AttributeEnd)
2364     {
2365         // Find the length of all attributes after this one, not counting the end marker
2366         ULONG LengthOfAttributes = 0;
2367         PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
2368         while (CurrentAttribute->Type != AttributeEnd)
2369         {
2370             LengthOfAttributes += CurrentAttribute->Length;
2371             CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
2372         }
2373 
2374         // Leave room for the existing attributes
2375         NewMaxIndexRootSize -= LengthOfAttributes;
2376     }
2377 
2378     // The index allocation and index bitmap may have grown, leaving less room for the index root,
2379     // so now we need to double-check that index root isn't too large
2380     NodeSize = GetSizeOfIndexEntries(NewTree->RootNode);
2381     if (NodeSize > NewMaxIndexRootSize)
2382     {
2383         DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize, NewMaxIndexRootSize);
2384 
2385         Status = DemoteBTreeRoot(NewTree);
2386         if (!NT_SUCCESS(Status))
2387         {
2388             DPRINT1("ERROR: Failed to demote index root!\n");
2389             DestroyBTree(NewTree);
2390             ReleaseAttributeContext(IndexRootContext);
2391             ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2392             ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2393             return Status;
2394         }
2395 
2396         // We need to update the index allocation once more
2397         Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
2398         if (!NT_SUCCESS(Status))
2399         {
2400             DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2401             DestroyBTree(NewTree);
2402             ReleaseAttributeContext(IndexRootContext);
2403             ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2404             ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2405             return Status;
2406         }
2407 
2408         // re-recalculate max size of index root
2409         NewMaxIndexRootSize =
2410             // Find the maximum index size given what the file record can hold
2411             // First, find the max index size assuming index root is the last attribute
2412             DeviceExt->NtfsInfo.BytesPerFileRecord               // Start with the size of a file record
2413             - IndexRootOffset                                    // Subtract the length of everything that comes before index root
2414             - IndexRootContext->pRecord->Resident.ValueOffset    // Subtract the length of the attribute header for index root
2415             - sizeof(INDEX_ROOT_ATTRIBUTE)                       // Subtract the length of the index root header
2416             - (sizeof(ULONG) * 2);                               // Subtract the length of the file record end marker and padding
2417 
2418                                                                  // Are there attributes after this one?
2419         NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
2420         if (NextAttribute->Type != AttributeEnd)
2421         {
2422             // Find the length of all attributes after this one, not counting the end marker
2423             ULONG LengthOfAttributes = 0;
2424             PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
2425             while (CurrentAttribute->Type != AttributeEnd)
2426             {
2427                 LengthOfAttributes += CurrentAttribute->Length;
2428                 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
2429             }
2430 
2431             // Leave room for the existing attributes
2432             NewMaxIndexRootSize -= LengthOfAttributes;
2433         }
2434 
2435 
2436     }
2437 
2438     // Create the Index Root from the B*Tree
2439     Status = CreateIndexRootFromBTree(DeviceExt, NewTree, NewMaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength);
2440     if (!NT_SUCCESS(Status))
2441     {
2442         DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
2443         DestroyBTree(NewTree);
2444         ReleaseAttributeContext(IndexRootContext);
2445         ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2446         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2447         return Status;
2448     }
2449 
2450     // We're done with the B-Tree now
2451     DestroyBTree(NewTree);
2452 
2453     // Write back the new index root attribute to the parent directory file record
2454 
2455     // First, we need to resize the attribute.
2456     // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
2457     // We can't set the size as we normally would, because $INDEX_ROOT must always be resident.
2458     AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
2459 
2460     if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength)
2461     {
2462         // Update the length of the attribute in the file record of the parent directory
2463         Status = InternalSetResidentAttributeLength(DeviceExt,
2464                                                     IndexRootContext,
2465                                                     ParentFileRecord,
2466                                                     IndexRootOffset,
2467                                                     AttributeLength);
2468         if (!NT_SUCCESS(Status))
2469         {
2470             ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2471             ReleaseAttributeContext(IndexRootContext);
2472             ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2473             ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2474             DPRINT1("ERROR: Unable to set resident attribute length!\n");
2475             return Status;
2476         }
2477 
2478     }
2479 
2480     NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord);
2481 
2482     Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2483     if (!NT_SUCCESS(Status))
2484     {
2485         DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex);
2486         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2487         ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2488         ReleaseAttributeContext(IndexRootContext);
2489         ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2490         return Status;
2491     }
2492 
2493     // Write the new index root to disk
2494     Status = WriteAttribute(DeviceExt,
2495                             IndexRootContext,
2496                             0,
2497                             (PUCHAR)NewIndexRoot,
2498                             AttributeLength,
2499                             &LengthWritten,
2500                             ParentFileRecord);
2501     if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength)
2502     {
2503         DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
2504         ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2505         ReleaseAttributeContext(IndexRootContext);
2506         ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2507         ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2508         return Status;
2509     }
2510 
2511     // re-read the parent file record, so we can dump it
2512     Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2513     if (!NT_SUCCESS(Status))
2514     {
2515         DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n");
2516     }
2517     else
2518     {
2519 #ifndef NDEBUG
2520         DPRINT1("Dumping new B-Tree:\n");
2521 
2522         Status = CreateBTreeFromIndex(DeviceExt, ParentFileRecord, IndexRootContext, NewIndexRoot, &NewTree);
2523         if (!NT_SUCCESS(Status))
2524         {
2525             DPRINT1("ERROR: Couldn't re-create b-tree\n");
2526             return Status;
2527         }
2528 
2529         DumpBTree(NewTree);
2530 
2531         DestroyBTree(NewTree);
2532 
2533         NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
2534 #endif
2535     }
2536 
2537     // Cleanup
2538     ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2539     ReleaseAttributeContext(IndexRootContext);
2540     ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2541     ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2542 
2543     return Status;
2544 }
2545 
2546 NTSTATUS
2547 AddFixupArray(PDEVICE_EXTENSION Vcb,
2548               PNTFS_RECORD_HEADER Record)
2549 {
2550     USHORT *pShortToFixUp;
2551     ULONG ArrayEntryCount = Record->UsaCount - 1;
2552     ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2;
2553     ULONG i;
2554 
2555     PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
2556 
2557     DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
2558 
2559     fixupArray->USN++;
2560 
2561     for (i = 0; i < ArrayEntryCount; i++)
2562     {
2563         DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
2564 
2565         pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
2566         fixupArray->Array[i] = *pShortToFixUp;
2567         *pShortToFixUp = fixupArray->USN;
2568         Offset += Vcb->NtfsInfo.BytesPerSector;
2569     }
2570 
2571     return STATUS_SUCCESS;
2572 }
2573 
2574 NTSTATUS
2575 ReadLCN(PDEVICE_EXTENSION Vcb,
2576         ULONGLONG lcn,
2577         ULONG count,
2578         PVOID buffer)
2579 {
2580     LARGE_INTEGER DiskSector;
2581 
2582     DiskSector.QuadPart = lcn;
2583 
2584     return NtfsReadSectors(Vcb->StorageDevice,
2585                            DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
2586                            count * Vcb->NtfsInfo.SectorsPerCluster,
2587                            Vcb->NtfsInfo.BytesPerSector,
2588                            buffer,
2589                            FALSE);
2590 }
2591 
2592 
2593 BOOLEAN
2594 CompareFileName(PUNICODE_STRING FileName,
2595                 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
2596                 BOOLEAN DirSearch,
2597                 BOOLEAN CaseSensitive)
2598 {
2599     BOOLEAN Ret, Alloc = FALSE;
2600     UNICODE_STRING EntryName;
2601 
2602     EntryName.Buffer = IndexEntry->FileName.Name;
2603     EntryName.Length =
2604     EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
2605 
2606     if (DirSearch)
2607     {
2608         UNICODE_STRING IntFileName;
2609         if (!CaseSensitive)
2610         {
2611             NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
2612             Alloc = TRUE;
2613         }
2614         else
2615         {
2616             IntFileName = *FileName;
2617         }
2618 
2619         Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL);
2620 
2621         if (Alloc)
2622         {
2623             RtlFreeUnicodeString(&IntFileName);
2624         }
2625 
2626         return Ret;
2627     }
2628     else
2629     {
2630         return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0);
2631     }
2632 }
2633 
2634 /**
2635 * @name UpdateMftMirror
2636 * @implemented
2637 *
2638 * Backs-up the first ~4 master file table entries to the $MFTMirr file.
2639 *
2640 * @param Vcb
2641 * Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated.
2642 *
2643 * @return
2644 
2645 * STATUS_SUCCESS on success.
2646 * STATUS_INSUFFICIENT_RESOURCES if an allocation failed.
2647 * STATUS_UNSUCCESSFUL if we couldn't read the master file table.
2648 *
2649 * @remarks
2650 * NTFS maintains up-to-date copies of the first several mft entries in the $MFTMirr file. Usually, the first 4 file
2651 * records from the mft are stored. The exact number of entries is determined by the size of $MFTMirr's $DATA.
2652 * If $MFTMirr is not up-to-date, chkdsk will reject every change it can find prior to when $MFTMirr was last updated.
2653 * Therefore, it's recommended to call this function if the volume changes considerably. For instance, IncreaseMftSize()
2654 * relies on this function to keep chkdsk from deleting the mft entries it creates. Note that under most instances, creating
2655 * or deleting a file will not affect the first ~four mft entries, and so will not require updating the mft mirror.
2656 */
2657 NTSTATUS
2658 UpdateMftMirror(PNTFS_VCB Vcb)
2659 {
2660     PFILE_RECORD_HEADER MirrorFileRecord;
2661     PNTFS_ATTR_CONTEXT MirrDataContext;
2662     PNTFS_ATTR_CONTEXT MftDataContext;
2663     PCHAR DataBuffer;
2664     ULONGLONG DataLength;
2665     NTSTATUS Status;
2666     ULONG BytesRead;
2667     ULONG LengthWritten;
2668 
2669     // Allocate memory for the Mft mirror file record
2670     MirrorFileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
2671     if (!MirrorFileRecord)
2672     {
2673         DPRINT1("Error: Failed to allocate memory for $MFTMirr!\n");
2674         return STATUS_INSUFFICIENT_RESOURCES;
2675     }
2676 
2677     // Read the Mft Mirror file record
2678     Status = ReadFileRecord(Vcb, NTFS_FILE_MFTMIRR, MirrorFileRecord);
2679     if (!NT_SUCCESS(Status))
2680     {
2681         DPRINT1("ERROR: Failed to read $MFTMirr!\n");
2682         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2683         return Status;
2684     }
2685 
2686     // Find the $DATA attribute of $MFTMirr
2687     Status = FindAttribute(Vcb, MirrorFileRecord, AttributeData, L"", 0, &MirrDataContext, NULL);
2688     if (!NT_SUCCESS(Status))
2689     {
2690         DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
2691         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2692         return Status;
2693     }
2694 
2695     // Find the $DATA attribute of $MFT
2696     Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeData, L"", 0, &MftDataContext, NULL);
2697     if (!NT_SUCCESS(Status))
2698     {
2699         DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
2700         ReleaseAttributeContext(MirrDataContext);
2701         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2702         return Status;
2703     }
2704 
2705     // Get the size of the mirror's $DATA attribute
2706     DataLength = AttributeDataLength(MirrDataContext->pRecord);
2707 
2708     ASSERT(DataLength % Vcb->NtfsInfo.BytesPerFileRecord == 0);
2709 
2710     // Create buffer for the mirror's $DATA attribute
2711     DataBuffer = ExAllocatePoolWithTag(NonPagedPool, DataLength, TAG_NTFS);
2712     if (!DataBuffer)
2713     {
2714         DPRINT1("Error: Couldn't allocate memory for $DATA buffer!\n");
2715         ReleaseAttributeContext(MftDataContext);
2716         ReleaseAttributeContext(MirrDataContext);
2717         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2718         return STATUS_INSUFFICIENT_RESOURCES;
2719     }
2720 
2721     ASSERT(DataLength < ULONG_MAX);
2722 
2723     // Back up the first several entries of the Mft's $DATA Attribute
2724     BytesRead = ReadAttribute(Vcb, MftDataContext, 0, DataBuffer, (ULONG)DataLength);
2725     if (BytesRead != (ULONG)DataLength)
2726     {
2727         DPRINT1("Error: Failed to read $DATA for $MFTMirr!\n");
2728         ReleaseAttributeContext(MftDataContext);
2729         ReleaseAttributeContext(MirrDataContext);
2730         ExFreePoolWithTag(DataBuffer, TAG_NTFS);
2731         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2732         return STATUS_UNSUCCESSFUL;
2733     }
2734 
2735     // Write the mirror's $DATA attribute
2736     Status = WriteAttribute(Vcb,
2737                              MirrDataContext,
2738                              0,
2739                              (PUCHAR)DataBuffer,
2740                              DataLength,
2741                              &LengthWritten,
2742                              MirrorFileRecord);
2743     if (!NT_SUCCESS(Status))
2744     {
2745         DPRINT1("ERROR: Failed to write $DATA attribute of $MFTMirr!\n");
2746     }
2747 
2748     // Cleanup
2749     ReleaseAttributeContext(MftDataContext);
2750     ReleaseAttributeContext(MirrDataContext);
2751     ExFreePoolWithTag(DataBuffer, TAG_NTFS);
2752     ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2753 
2754     return Status;
2755 }
2756 
2757 #if 0
2758 static
2759 VOID
2760 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
2761 {
2762     DPRINT1("Entry: %p\n", IndexEntry);
2763     DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
2764     DPRINT1("\tLength: %u\n", IndexEntry->Length);
2765     DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
2766     DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
2767     DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
2768     DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
2769     DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
2770     DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
2771     DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
2772     DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
2773     DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
2774     DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
2775     DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
2776     DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
2777     DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
2778     DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
2779 }
2780 #endif
2781 
2782 NTSTATUS
2783 BrowseSubNodeIndexEntries(PNTFS_VCB Vcb,
2784                           PFILE_RECORD_HEADER MftRecord,
2785                           ULONG IndexBlockSize,
2786                           PUNICODE_STRING FileName,
2787                           PNTFS_ATTR_CONTEXT IndexAllocationContext,
2788                           PRTL_BITMAP Bitmap,
2789                           ULONGLONG VCN,
2790                           PULONG StartEntry,
2791                           PULONG CurrentEntry,
2792                           BOOLEAN DirSearch,
2793                           BOOLEAN CaseSensitive,
2794                           ULONGLONG *OutMFTIndex)
2795 {
2796     PINDEX_BUFFER IndexRecord;
2797     ULONGLONG Offset;
2798     ULONG BytesRead;
2799     PINDEX_ENTRY_ATTRIBUTE FirstEntry;
2800     PINDEX_ENTRY_ATTRIBUTE LastEntry;
2801     PINDEX_ENTRY_ATTRIBUTE IndexEntry;
2802     ULONG NodeNumber;
2803     NTSTATUS Status;
2804 
2805     DPRINT("BrowseSubNodeIndexEntries(%p, %p, %lu, %wZ, %p, %p, %I64d, %lu, %lu, %s, %s, %p)\n",
2806            Vcb,
2807            MftRecord,
2808            IndexBlockSize,
2809            FileName,
2810            IndexAllocationContext,
2811            Bitmap,
2812            VCN,
2813            *StartEntry,
2814            *CurrentEntry,
2815            "FALSE",
2816            DirSearch ? "TRUE" : "FALSE",
2817            CaseSensitive ? "TRUE" : "FALSE",
2818            OutMFTIndex);
2819 
2820     // Calculate node number as VCN / Clusters per index record
2821     NodeNumber = VCN / (Vcb->NtfsInfo.BytesPerIndexRecord / Vcb->NtfsInfo.BytesPerCluster);
2822 
2823     // Is the bit for this node clear in the bitmap?
2824     if (!RtlCheckBit(Bitmap, NodeNumber))
2825     {
2826         DPRINT1("File system corruption detected, node with VCN %I64u is being reused or is marked as deleted.\n", VCN);
2827         return STATUS_DATA_ERROR;
2828     }
2829 
2830     // Clear the bit for this node so it can't be recursively referenced
2831     RtlClearBits(Bitmap, NodeNumber, 1);
2832 
2833     // Allocate memory for the index record
2834     IndexRecord = ExAllocatePoolWithTag(NonPagedPool, IndexBlockSize, TAG_NTFS);
2835     if (!IndexRecord)
2836     {
2837         DPRINT1("Unable to allocate memory for index record!\n");
2838         return STATUS_INSUFFICIENT_RESOURCES;
2839     }
2840 
2841     // Calculate offset of index record
2842     Offset = VCN * Vcb->NtfsInfo.BytesPerCluster;
2843 
2844     // Read the index record
2845     BytesRead = ReadAttribute(Vcb, IndexAllocationContext, Offset, (PCHAR)IndexRecord, IndexBlockSize);
2846     if (BytesRead != IndexBlockSize)
2847     {
2848         DPRINT1("Unable to read index record!\n");
2849         ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2850         return STATUS_UNSUCCESSFUL;
2851     }
2852 
2853     // Assert that we're dealing with an index record here
2854     ASSERT(IndexRecord->Ntfs.Type == NRH_INDX_TYPE);
2855 
2856     // Apply the fixup array to the index record
2857     Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
2858     if (!NT_SUCCESS(Status))
2859     {
2860         ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2861         DPRINT1("Failed to apply fixup array!\n");
2862         return Status;
2863     }
2864 
2865     ASSERT(IndexRecord->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
2866     FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.FirstEntryOffset);
2867     LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.TotalSizeOfEntries);
2868     ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRecord + IndexBlockSize));
2869 
2870     // Loop through all Index Entries of index, starting with FirstEntry
2871     IndexEntry = FirstEntry;
2872     while (IndexEntry <= LastEntry)
2873     {
2874         // Does IndexEntry have a sub-node?
2875         if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
2876         {
2877             if (!(IndexRecord->Header.Flags & INDEX_NODE_LARGE) || !IndexAllocationContext)
2878             {
2879                 DPRINT1("Filesystem corruption detected!\n");
2880             }
2881             else
2882             {
2883                 Status = BrowseSubNodeIndexEntries(Vcb,
2884                                                    MftRecord,
2885                                                    IndexBlockSize,
2886                                                    FileName,
2887                                                    IndexAllocationContext,
2888                                                    Bitmap,
2889                                                    GetIndexEntryVCN(IndexEntry),
2890                                                    StartEntry,
2891                                                    CurrentEntry,
2892                                                    DirSearch,
2893                                                    CaseSensitive,
2894                                                    OutMFTIndex);
2895                 if (NT_SUCCESS(Status))
2896                 {
2897                     ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2898                     return Status;
2899                 }
2900             }
2901         }
2902 
2903         // Are we done?
2904         if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END)
2905             break;
2906 
2907         // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria
2908         if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE &&
2909             *CurrentEntry >= *StartEntry &&
2910             IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
2911             CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
2912         {
2913             *StartEntry = *CurrentEntry;
2914             *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
2915             ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2916             return STATUS_SUCCESS;
2917         }
2918 
2919         // Advance to the next index entry
2920         (*CurrentEntry) += 1;
2921         ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
2922         IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
2923     }
2924 
2925     ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2926 
2927     return STATUS_OBJECT_PATH_NOT_FOUND;
2928 }
2929 
2930 NTSTATUS
2931 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
2932                    PFILE_RECORD_HEADER MftRecord,
2933                    PINDEX_ROOT_ATTRIBUTE IndexRecord,
2934                    ULONG IndexBlockSize,
2935                    PINDEX_ENTRY_ATTRIBUTE FirstEntry,
2936                    PINDEX_ENTRY_ATTRIBUTE LastEntry,
2937                    PUNICODE_STRING FileName,
2938                    PULONG StartEntry,
2939                    PULONG CurrentEntry,
2940                    BOOLEAN DirSearch,
2941                    BOOLEAN CaseSensitive,
2942                    ULONGLONG *OutMFTIndex)
2943 {
2944     NTSTATUS Status;
2945     PINDEX_ENTRY_ATTRIBUTE IndexEntry;
2946     PNTFS_ATTR_CONTEXT IndexAllocationContext;
2947     PNTFS_ATTR_CONTEXT BitmapContext;
2948     PCHAR *BitmapMem;
2949     ULONG *BitmapPtr;
2950     RTL_BITMAP  Bitmap;
2951 
2952     DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
2953            Vcb,
2954            MftRecord,
2955            IndexRecord,
2956            IndexBlockSize,
2957            FirstEntry,
2958            LastEntry,
2959            FileName,
2960            *StartEntry,
2961            *CurrentEntry,
2962            DirSearch ? "TRUE" : "FALSE",
2963            CaseSensitive ? "TRUE" : "FALSE",
2964            OutMFTIndex);
2965 
2966     // Find the $I30 index allocation, if there is one
2967     Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL);
2968     if (NT_SUCCESS(Status))
2969     {
2970         ULONGLONG BitmapLength;
2971         // Find the bitmap attribute for the index
2972         Status = FindAttribute(Vcb, MftRecord, AttributeBitmap, L"$I30", 4, &BitmapContext, NULL);
2973         if (!NT_SUCCESS(Status))
2974         {
2975             DPRINT1("Potential file system corruption detected!\n");
2976             ReleaseAttributeContext(IndexAllocationContext);
2977             return Status;
2978         }
2979 
2980         // Get the length of the bitmap attribute
2981         BitmapLength = AttributeDataLength(BitmapContext->pRecord);
2982 
2983         // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer
2984         // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple.
2985         BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BitmapLength + sizeof(ULONG), TAG_NTFS);
2986         if (!BitmapMem)
2987         {
2988             DPRINT1("Error: failed to allocate bitmap!");
2989             ReleaseAttributeContext(BitmapContext);
2990             ReleaseAttributeContext(IndexAllocationContext);
2991             return STATUS_INSUFFICIENT_RESOURCES;
2992         }
2993 
2994         RtlZeroMemory(BitmapMem, BitmapLength + sizeof(ULONG));
2995 
2996         // RtlInitializeBitmap() wants a pointer that's ULONG-aligned.
2997         BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG));
2998 
2999         // Read the existing bitmap data
3000         Status = ReadAttribute(Vcb, BitmapContext, 0, (PCHAR)BitmapPtr, BitmapLength);
3001         if (!NT_SUCCESS(Status))
3002         {
3003             DPRINT1("ERROR: Failed to read bitmap attribute!\n");
3004             ExFreePoolWithTag(BitmapMem, TAG_NTFS);
3005             ReleaseAttributeContext(BitmapContext);
3006             ReleaseAttributeContext(IndexAllocationContext);
3007             return Status;
3008         }
3009 
3010         // Initialize bitmap
3011         RtlInitializeBitMap(&Bitmap, BitmapPtr, BitmapLength * 8);
3012     }
3013     else
3014     {
3015         // Couldn't find an index allocation
3016         IndexAllocationContext = NULL;
3017     }
3018 
3019 
3020     // Loop through all Index Entries of index, starting with FirstEntry
3021     IndexEntry = FirstEntry;
3022     while (IndexEntry <= LastEntry)
3023     {
3024         // Does IndexEntry have a sub-node?
3025         if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
3026         {
3027             if (!(IndexRecord->Header.Flags & INDEX_ROOT_LARGE) || !IndexAllocationContext)
3028             {
3029                 DPRINT1("Filesystem corruption detected!\n");
3030             }
3031             else
3032             {
3033                 Status = BrowseSubNodeIndexEntries(Vcb,
3034                                                    MftRecord,
3035                                                    IndexBlockSize,
3036                                                    FileName,
3037                                                    IndexAllocationContext,
3038                                                    &Bitmap,
3039                                                    GetIndexEntryVCN(IndexEntry),
3040                                                    StartEntry,
3041                                                    CurrentEntry,
3042                                                    DirSearch,
3043                                                    CaseSensitive,
3044                                                    OutMFTIndex);
3045                 if (NT_SUCCESS(Status))
3046                 {
3047                     ExFreePoolWithTag(BitmapMem, TAG_NTFS);
3048                     ReleaseAttributeContext(BitmapContext);
3049                     ReleaseAttributeContext(IndexAllocationContext);
3050                     return Status;
3051                 }
3052             }
3053         }
3054 
3055         // Are we done?
3056         if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END)
3057             break;
3058 
3059         // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria
3060         if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE &&
3061             *CurrentEntry >= *StartEntry &&
3062             IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
3063             CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
3064         {
3065             *StartEntry = *CurrentEntry;
3066             *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
3067             if (IndexAllocationContext)
3068             {
3069                 ExFreePoolWithTag(BitmapMem, TAG_NTFS);
3070                 ReleaseAttributeContext(BitmapContext);
3071                 ReleaseAttributeContext(IndexAllocationContext);
3072             }
3073             return STATUS_SUCCESS;
3074         }
3075 
3076         // Advance to the next index entry
3077         (*CurrentEntry) += 1;
3078         ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
3079         IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
3080     }
3081 
3082     if (IndexAllocationContext)
3083     {
3084         ExFreePoolWithTag(BitmapMem, TAG_NTFS);
3085         ReleaseAttributeContext(BitmapContext);
3086         ReleaseAttributeContext(IndexAllocationContext);
3087     }
3088 
3089     return STATUS_OBJECT_PATH_NOT_FOUND;
3090 }
3091 
3092 NTSTATUS
3093 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
3094                   ULONGLONG MFTIndex,
3095                   PUNICODE_STRING FileName,
3096                   PULONG FirstEntry,
3097                   BOOLEAN DirSearch,
3098                   BOOLEAN CaseSensitive,
3099                   ULONGLONG *OutMFTIndex)
3100 {
3101     PFILE_RECORD_HEADER MftRecord;
3102     PNTFS_ATTR_CONTEXT IndexRootCtx;
3103     PINDEX_ROOT_ATTRIBUTE IndexRoot;
3104     PCHAR IndexRecord;
3105     PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
3106     NTSTATUS Status;
3107     ULONG CurrentEntry = 0;
3108 
3109     DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n",
3110            Vcb,
3111            MFTIndex,
3112            FileName,
3113            *FirstEntry,
3114            DirSearch ? "TRUE" : "FALSE",
3115            CaseSensitive ? "TRUE" : "FALSE",
3116            OutMFTIndex);
3117 
3118     MftRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
3119     if (MftRecord == NULL)
3120     {
3121         return STATUS_INSUFFICIENT_RESOURCES;
3122     }
3123 
3124     Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
3125     if (!NT_SUCCESS(Status))
3126     {
3127         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
3128         return Status;
3129     }
3130 
3131     ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
3132     Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
3133     if (!NT_SUCCESS(Status))
3134     {
3135         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
3136         return Status;
3137     }
3138 
3139     IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
3140     if (IndexRecord == NULL)
3141     {
3142         ReleaseAttributeContext(IndexRootCtx);
3143         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
3144         return STATUS_INSUFFICIENT_RESOURCES;
3145     }
3146 
3147     ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
3148     IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
3149     IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
3150     /* Index root is always resident. */
3151     IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
3152     ReleaseAttributeContext(IndexRootCtx);
3153 
3154     DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
3155 
3156     Status = BrowseIndexEntries(Vcb,
3157                                 MftRecord,
3158                                 (PINDEX_ROOT_ATTRIBUTE)IndexRecord,
3159                                 IndexRoot->SizeOfEntry,
3160                                 IndexEntry,
3161                                 IndexEntryEnd,
3162                                 FileName,
3163                                 FirstEntry,
3164                                 &CurrentEntry,
3165                                 DirSearch,
3166                                 CaseSensitive,
3167                                 OutMFTIndex);
3168 
3169     ExFreePoolWithTag(IndexRecord, TAG_NTFS);
3170     ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
3171 
3172     return Status;
3173 }
3174 
3175 NTSTATUS
3176 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
3177                  PUNICODE_STRING PathName,
3178                  BOOLEAN CaseSensitive,
3179                  PFILE_RECORD_HEADER *FileRecord,
3180                  PULONGLONG MFTIndex,
3181                  ULONGLONG CurrentMFTIndex)
3182 {
3183     UNICODE_STRING Current, Remaining;
3184     NTSTATUS Status;
3185     ULONG FirstEntry = 0;
3186 
3187     DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
3188            Vcb,
3189            PathName,
3190            CaseSensitive ? "TRUE" : "FALSE",
3191            FileRecord,
3192            MFTIndex,
3193            CurrentMFTIndex);
3194 
3195     FsRtlDissectName(*PathName, &Current, &Remaining);
3196 
3197     while (Current.Length != 0)
3198     {
3199         DPRINT("Current: %wZ\n", &Current);
3200 
3201         Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex);
3202         if (!NT_SUCCESS(Status))
3203         {
3204             return Status;
3205         }
3206 
3207         if (Remaining.Length == 0)
3208             break;
3209 
3210         FsRtlDissectName(Current, &Current, &Remaining);
3211     }
3212 
3213     *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
3214     if (*FileRecord == NULL)
3215     {
3216         DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
3217         return STATUS_INSUFFICIENT_RESOURCES;
3218     }
3219 
3220     Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
3221     if (!NT_SUCCESS(Status))
3222     {
3223         DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
3224         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord);
3225         return Status;
3226     }
3227 
3228     *MFTIndex = CurrentMFTIndex;
3229 
3230     return STATUS_SUCCESS;
3231 }
3232 
3233 NTSTATUS
3234 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
3235                PUNICODE_STRING PathName,
3236                BOOLEAN CaseSensitive,
3237                PFILE_RECORD_HEADER *FileRecord,
3238                PULONGLONG MFTIndex)
3239 {
3240     return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT);
3241 }
3242 
3243 void
3244 NtfsDumpData(ULONG_PTR Buffer, ULONG Length)
3245 {
3246     ULONG i, j;
3247 
3248     // dump binary data, 8 bytes at a time
3249     for (i = 0; i < Length; i += 8)
3250     {
3251         // display current offset, in hex
3252         DbgPrint("\t%03x\t", i);
3253 
3254         // display hex value of each of the next 8 bytes
3255         for (j = 0; j < 8; j++)
3256             DbgPrint("%02x ", *(PUCHAR)(Buffer + i + j));
3257         DbgPrint("\n");
3258     }
3259 }
3260 
3261 /**
3262 * @name NtfsDumpFileRecord
3263 * @implemented
3264 *
3265 * Provides diagnostic information about a file record. Prints a hex dump
3266 * of the entire record (based on the size reported by FileRecord->ByesInUse),
3267 * then prints a dump of each attribute.
3268 *
3269 * @param Vcb
3270 * Pointer to a DEVICE_EXTENSION describing the volume.
3271 *
3272 * @param FileRecord
3273 * Pointer to the file record to be analyzed.
3274 *
3275 * @remarks
3276 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
3277 * in size, and not just the header.
3278 *
3279 */
3280 VOID
3281 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
3282                    PFILE_RECORD_HEADER FileRecord)
3283 {
3284     ULONG i, j;
3285 
3286     // dump binary data, 8 bytes at a time
3287     for (i = 0; i < FileRecord->BytesInUse; i += 8)
3288     {
3289         // display current offset, in hex
3290         DbgPrint("\t%03x\t", i);
3291 
3292         // display hex value of each of the next 8 bytes
3293         for (j = 0; j < 8; j++)
3294             DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
3295         DbgPrint("\n");
3296     }
3297 
3298     NtfsDumpFileAttributes(Vcb, FileRecord);
3299 }
3300 
3301 NTSTATUS
3302 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
3303                PUNICODE_STRING SearchPattern,
3304                PULONG FirstEntry,
3305                PFILE_RECORD_HEADER *FileRecord,
3306                PULONGLONG MFTIndex,
3307                ULONGLONG CurrentMFTIndex,
3308                BOOLEAN CaseSensitive)
3309 {
3310     NTSTATUS Status;
3311 
3312     DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n",
3313            Vcb,
3314            SearchPattern,
3315            *FirstEntry,
3316            FileRecord,
3317            MFTIndex,
3318            CurrentMFTIndex,
3319            (CaseSensitive ? "TRUE" : "FALSE"));
3320 
3321     Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, CaseSensitive, &CurrentMFTIndex);
3322     if (!NT_SUCCESS(Status))
3323     {
3324         DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
3325         return Status;
3326     }
3327 
3328     *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
3329     if (*FileRecord == NULL)
3330     {
3331         DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
3332         return STATUS_INSUFFICIENT_RESOURCES;
3333     }
3334 
3335     Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
3336     if (!NT_SUCCESS(Status))
3337     {
3338         DPRINT("NtfsFindFileAt: Can't read MFT record\n");
3339         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord);
3340         return Status;
3341     }
3342 
3343     *MFTIndex = CurrentMFTIndex;
3344 
3345     return STATUS_SUCCESS;
3346 }
3347 
3348 /* EOF */
3349