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