xref: /reactos/drivers/filesystems/ntfs/attrib.c (revision 02e84521)
1 /*
2  *  ReactOS kernel
3  *  Copyright (C) 2002,2003 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/attrib.c
22  * PURPOSE:          NTFS filesystem driver
23  * PROGRAMMERS:      Eric Kohl
24  *                   Valentin Verkhovsky
25  *                   Hervé Poussineau (hpoussin@reactos.org)
26  *                   Pierre Schweitzer (pierre@reactos.org)
27  */
28 
29 /* INCLUDES *****************************************************************/
30 
31 #include "ntfs.h"
32 #include <ntintsafe.h>
33 
34 #define NDEBUG
35 #include <debug.h>
36 
37 /* FUNCTIONS ****************************************************************/
38 
39 /**
40 * @name AddBitmap
41 * @implemented
42 *
43 * Adds a $BITMAP attribute to a given FileRecord.
44 *
45 * @param Vcb
46 * Pointer to an NTFS_VCB for the destination volume.
47 *
48 * @param FileRecord
49 * Pointer to a complete file record to add the attribute to.
50 *
51 * @param AttributeAddress
52 * Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute.
53 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
54 *
55 * @param Name
56 * Pointer to a string of 16-bit Unicode characters naming the attribute. Most often L"$I30".
57 *
58 * @param NameLength
59 * The number of wide-characters in the name. L"$I30" Would use 4 here.
60 *
61 * @return
62 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
63 * of the given file record, or if the file record isn't large enough for the attribute.
64 *
65 * @remarks
66 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
67 * be of type AttributeEnd.
68 * This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space.
69 *
70 */
71 NTSTATUS
72 AddBitmap(PNTFS_VCB Vcb,
73           PFILE_RECORD_HEADER FileRecord,
74           PNTFS_ATTR_RECORD AttributeAddress,
75           PCWSTR Name,
76           USHORT NameLength)
77 {
78     ULONG AttributeLength;
79     // Calculate the header length
80     ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
81     ULONG FileRecordEnd = AttributeAddress->Length;
82     ULONG NameOffset;
83     ULONG ValueOffset;
84     // We'll start out with 8 bytes of bitmap data
85     ULONG ValueLength = 8;
86     ULONG BytesAvailable;
87 
88     if (AttributeAddress->Type != AttributeEnd)
89     {
90         DPRINT1("FIXME: Can only add $BITMAP attribute to the end of a file record.\n");
91         return STATUS_NOT_IMPLEMENTED;
92     }
93 
94     NameOffset = ResidentHeaderLength;
95 
96     // Calculate ValueOffset, which will be aligned to a 4-byte boundary
97     ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT);
98 
99     // Calculate length of attribute
100     AttributeLength = ValueOffset + ValueLength;
101     AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT);
102 
103     // Make sure the file record is large enough for the new attribute
104     BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
105     if (BytesAvailable < AttributeLength)
106     {
107         DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
108         return STATUS_NOT_IMPLEMENTED;
109     }
110 
111     // Set Attribute fields
112     RtlZeroMemory(AttributeAddress, AttributeLength);
113 
114     AttributeAddress->Type = AttributeBitmap;
115     AttributeAddress->Length = AttributeLength;
116     AttributeAddress->NameLength = NameLength;
117     AttributeAddress->NameOffset = NameOffset;
118     AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
119 
120     AttributeAddress->Resident.ValueLength = ValueLength;
121     AttributeAddress->Resident.ValueOffset = ValueOffset;
122 
123     // Set the name
124     RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR));
125 
126     // move the attribute-end and file-record-end markers to the end of the file record
127     AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
128     SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
129 
130     return STATUS_SUCCESS;
131 }
132 
133 /**
134 * @name AddData
135 * @implemented
136 *
137 * Adds a $DATA attribute to a given FileRecord.
138 *
139 * @param FileRecord
140 * Pointer to a complete file record to add the attribute to. Caller is responsible for
141 * ensuring FileRecord is large enough to contain $DATA.
142 *
143 * @param AttributeAddress
144 * Pointer to the region of memory that will receive the $DATA attribute.
145 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
146 *
147 * @return
148 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
149 * of the given file record.
150 *
151 * @remarks
152 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
153 * be of type AttributeEnd.
154 * As it's implemented, this function is only intended to assist in creating new file records. It
155 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
156 * It's the caller's responsibility to ensure the given file record has enough memory allocated
157 * for the attribute.
158 */
159 NTSTATUS
160 AddData(PFILE_RECORD_HEADER FileRecord,
161         PNTFS_ATTR_RECORD AttributeAddress)
162 {
163     ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
164     ULONG FileRecordEnd = AttributeAddress->Length;
165 
166     if (AttributeAddress->Type != AttributeEnd)
167     {
168         DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
169         return STATUS_NOT_IMPLEMENTED;
170     }
171 
172     AttributeAddress->Type = AttributeData;
173     AttributeAddress->Length = ResidentHeaderLength;
174     AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
175     AttributeAddress->Resident.ValueLength = 0;
176     AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
177 
178     // for unnamed $DATA attributes, NameOffset equals header length
179     AttributeAddress->NameOffset = ResidentHeaderLength;
180     AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
181 
182     // move the attribute-end and file-record-end markers to the end of the file record
183     AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
184     SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
185 
186     return STATUS_SUCCESS;
187 }
188 
189 /**
190 * @name AddFileName
191 * @implemented
192 *
193 * Adds a $FILE_NAME attribute to a given FileRecord.
194 *
195 * @param FileRecord
196 * Pointer to a complete file record to add the attribute to. Caller is responsible for
197 * ensuring FileRecord is large enough to contain $FILE_NAME.
198 *
199 * @param AttributeAddress
200 * Pointer to the region of memory that will receive the $FILE_NAME attribute.
201 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
202 *
203 * @param DeviceExt
204 * Points to the target disk's DEVICE_EXTENSION.
205 *
206 * @param FileObject
207 * Pointer to the FILE_OBJECT which represents the new name.
208 * This parameter is used to determine the filename and parent directory.
209 *
210 * @param CaseSensitive
211 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
212 * if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag.
213 *
214 * @param ParentMftIndex
215 * Pointer to a ULONGLONG which will receive the index of the parent directory.
216 *
217 * @return
218 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
219 * of the given file record.
220 *
221 * @remarks
222 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
223 * be of type AttributeEnd.
224 * As it's implemented, this function is only intended to assist in creating new file records. It
225 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
226 * It's the caller's responsibility to ensure the given file record has enough memory allocated
227 * for the attribute.
228 */
229 NTSTATUS
230 AddFileName(PFILE_RECORD_HEADER FileRecord,
231             PNTFS_ATTR_RECORD AttributeAddress,
232             PDEVICE_EXTENSION DeviceExt,
233             PFILE_OBJECT FileObject,
234             BOOLEAN CaseSensitive,
235             PULONGLONG ParentMftIndex)
236 {
237     ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
238     PFILENAME_ATTRIBUTE FileNameAttribute;
239     LARGE_INTEGER SystemTime;
240     ULONG FileRecordEnd = AttributeAddress->Length;
241     ULONGLONG CurrentMFTIndex = NTFS_FILE_ROOT;
242     UNICODE_STRING Current, Remaining, FilenameNoPath;
243     NTSTATUS Status = STATUS_SUCCESS;
244     ULONG FirstEntry;
245 
246     if (AttributeAddress->Type != AttributeEnd)
247     {
248         DPRINT1("FIXME: Can only add $FILE_NAME attribute to the end of a file record.\n");
249         return STATUS_NOT_IMPLEMENTED;
250     }
251 
252     AttributeAddress->Type = AttributeFileName;
253     AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
254 
255     FileNameAttribute = (PFILENAME_ATTRIBUTE)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
256 
257     // set timestamps
258     KeQuerySystemTime(&SystemTime);
259     FileNameAttribute->CreationTime = SystemTime.QuadPart;
260     FileNameAttribute->ChangeTime = SystemTime.QuadPart;
261     FileNameAttribute->LastWriteTime = SystemTime.QuadPart;
262     FileNameAttribute->LastAccessTime = SystemTime.QuadPart;
263 
264     // Is this a directory?
265     if(FileRecord->Flags & FRH_DIRECTORY)
266         FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_DIRECTORY;
267     else
268         FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE;
269 
270     // we need to extract the filename from the path
271     DPRINT1("Pathname: %wZ\n", &FileObject->FileName);
272 
273     FsRtlDissectName(FileObject->FileName, &Current, &Remaining);
274 
275     FilenameNoPath.Buffer = Current.Buffer;
276     FilenameNoPath.MaximumLength = FilenameNoPath.Length = Current.Length;
277 
278     while (Current.Length != 0)
279     {
280         DPRINT1("Current: %wZ\n", &Current);
281 
282         if (Remaining.Length != 0)
283         {
284             FilenameNoPath.Buffer = Remaining.Buffer;
285             FilenameNoPath.Length = FilenameNoPath.MaximumLength = Remaining.Length;
286         }
287 
288         FirstEntry = 0;
289         Status = NtfsFindMftRecord(DeviceExt,
290                                    CurrentMFTIndex,
291                                    &Current,
292                                    &FirstEntry,
293                                    FALSE,
294                                    CaseSensitive,
295                                    &CurrentMFTIndex);
296         if (!NT_SUCCESS(Status))
297             break;
298 
299         if (Remaining.Length == 0 )
300         {
301             if (Current.Length != 0)
302             {
303                 FilenameNoPath.Buffer = Current.Buffer;
304                 FilenameNoPath.Length = FilenameNoPath.MaximumLength = Current.Length;
305             }
306             break;
307         }
308 
309         FsRtlDissectName(Remaining, &Current, &Remaining);
310     }
311 
312     DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex);
313 
314     // set reference to parent directory
315     FileNameAttribute->DirectoryFileReferenceNumber = CurrentMFTIndex;
316     *ParentMftIndex = CurrentMFTIndex;
317 
318     DPRINT1("SequenceNumber: 0x%02x\n", FileRecord->SequenceNumber);
319 
320     // The highest 2 bytes should be the sequence number, unless the parent happens to be root
321     if (CurrentMFTIndex == NTFS_FILE_ROOT)
322         FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)NTFS_FILE_ROOT << 48;
323     else
324         FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)FileRecord->SequenceNumber << 48;
325 
326     DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber);
327 
328     FileNameAttribute->NameLength = FilenameNoPath.Length / sizeof(WCHAR);
329     RtlCopyMemory(FileNameAttribute->Name, FilenameNoPath.Buffer, FilenameNoPath.Length);
330 
331     // For now, we're emulating the way Windows behaves when 8.3 name generation is disabled
332     // TODO: add DOS Filename as needed
333     if (!CaseSensitive && RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL))
334         FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS;
335     else
336         FileNameAttribute->NameType = NTFS_FILE_NAME_POSIX;
337 
338     FileRecord->LinkCount++;
339 
340     AttributeAddress->Length = ResidentHeaderLength +
341         FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
342     AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
343 
344     AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
345     AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
346     AttributeAddress->Resident.Flags = RA_INDEXED;
347 
348     // move the attribute-end and file-record-end markers to the end of the file record
349     AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
350     SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
351 
352     return Status;
353 }
354 
355 /**
356 * @name AddIndexAllocation
357 * @implemented
358 *
359 * Adds an $INDEX_ALLOCATION attribute to a given FileRecord.
360 *
361 * @param Vcb
362 * Pointer to an NTFS_VCB for the destination volume.
363 *
364 * @param FileRecord
365 * Pointer to a complete file record to add the attribute to.
366 *
367 * @param AttributeAddress
368 * Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute.
369 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
370 *
371 * @param Name
372 * Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30".
373 *
374 * @param NameLength
375 * The number of wide-characters in the name. L"$I30" Would use 4 here.
376 *
377 * @return
378 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
379 * of the given file record, or if the file record isn't large enough for the attribute.
380 *
381 * @remarks
382 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
383 * be of type AttributeEnd.
384 * This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space.
385 *
386 */
387 NTSTATUS
388 AddIndexAllocation(PNTFS_VCB Vcb,
389                    PFILE_RECORD_HEADER FileRecord,
390                    PNTFS_ATTR_RECORD AttributeAddress,
391                    PCWSTR Name,
392                    USHORT NameLength)
393 {
394     ULONG RecordLength;
395     ULONG FileRecordEnd;
396     ULONG NameOffset;
397     ULONG DataRunOffset;
398     ULONG BytesAvailable;
399 
400     if (AttributeAddress->Type != AttributeEnd)
401     {
402         DPRINT1("FIXME: Can only add $INDEX_ALLOCATION attribute to the end of a file record.\n");
403         return STATUS_NOT_IMPLEMENTED;
404     }
405 
406     // Calculate the name offset
407     NameOffset = FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize);
408 
409     // Calculate the offset to the first data run
410     DataRunOffset = (sizeof(WCHAR) * NameLength) + NameOffset;
411     // The data run offset must be aligned to a 4-byte boundary
412     DataRunOffset = ALIGN_UP_BY(DataRunOffset, DATA_RUN_ALIGNMENT);
413 
414     // Calculate the length of the new attribute; the empty data run will consist of a single byte
415     RecordLength = DataRunOffset + 1;
416 
417     // The size of the attribute itself must be aligned to an 8 - byte boundary
418     RecordLength = ALIGN_UP_BY(RecordLength, ATTR_RECORD_ALIGNMENT);
419 
420     // Back up the last 4-bytes of the file record (even though this value doesn't matter)
421     FileRecordEnd = AttributeAddress->Length;
422 
423     // Make sure the file record can contain the new attribute
424     BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
425     if (BytesAvailable < RecordLength)
426     {
427         DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
428         return STATUS_NOT_IMPLEMENTED;
429     }
430 
431     // Set fields of attribute header
432     RtlZeroMemory(AttributeAddress, RecordLength);
433 
434     AttributeAddress->Type = AttributeIndexAllocation;
435     AttributeAddress->Length = RecordLength;
436     AttributeAddress->IsNonResident = TRUE;
437     AttributeAddress->NameLength = NameLength;
438     AttributeAddress->NameOffset = NameOffset;
439     AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
440 
441     AttributeAddress->NonResident.MappingPairsOffset = DataRunOffset;
442     AttributeAddress->NonResident.HighestVCN = (LONGLONG)-1;
443 
444     // Set the name
445     RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR));
446 
447     // move the attribute-end and file-record-end markers to the end of the file record
448     AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
449     SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
450 
451     return STATUS_SUCCESS;
452 }
453 
454 /**
455 * @name AddIndexRoot
456 * @implemented
457 *
458 * Adds an $INDEX_ROOT attribute to a given FileRecord.
459 *
460 * @param Vcb
461 * Pointer to an NTFS_VCB for the destination volume.
462 *
463 * @param FileRecord
464 * Pointer to a complete file record to add the attribute to. Caller is responsible for
465 * ensuring FileRecord is large enough to contain $INDEX_ROOT.
466 *
467 * @param AttributeAddress
468 * Pointer to the region of memory that will receive the $INDEX_ROOT attribute.
469 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
470 *
471 * @param NewIndexRoot
472 * Pointer to an INDEX_ROOT_ATTRIBUTE containing the index root that will be copied to the new attribute.
473 *
474 * @param RootLength
475 * The length of NewIndexRoot, in bytes.
476 *
477 * @param Name
478 * Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30".
479 *
480 * @param NameLength
481 * The number of wide-characters in the name. L"$I30" Would use 4 here.
482 *
483 * @return
484 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
485 * of the given file record.
486 *
487 * @remarks
488 * This function is intended to assist in creating new folders.
489 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
490 * be of type AttributeEnd.
491 * It's the caller's responsibility to ensure the given file record has enough memory allocated
492 * for the attribute, and this memory must have been zeroed.
493 */
494 NTSTATUS
495 AddIndexRoot(PNTFS_VCB Vcb,
496              PFILE_RECORD_HEADER FileRecord,
497              PNTFS_ATTR_RECORD AttributeAddress,
498              PINDEX_ROOT_ATTRIBUTE NewIndexRoot,
499              ULONG RootLength,
500              PCWSTR Name,
501              USHORT NameLength)
502 {
503     ULONG AttributeLength;
504     // Calculate the header length
505     ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
506     // Back up the file record's final ULONG (even though it doesn't matter)
507     ULONG FileRecordEnd = AttributeAddress->Length;
508     ULONG NameOffset;
509     ULONG ValueOffset;
510     ULONG BytesAvailable;
511 
512     if (AttributeAddress->Type != AttributeEnd)
513     {
514         DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
515         return STATUS_NOT_IMPLEMENTED;
516     }
517 
518     NameOffset = ResidentHeaderLength;
519 
520     // Calculate ValueOffset, which will be aligned to a 4-byte boundary
521     ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT);
522 
523     // Calculate length of attribute
524     AttributeLength = ValueOffset + RootLength;
525     AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT);
526 
527     // Make sure the file record is large enough for the new attribute
528     BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
529     if (BytesAvailable < AttributeLength)
530     {
531         DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
532         return STATUS_NOT_IMPLEMENTED;
533     }
534 
535     // Set Attribute fields
536     RtlZeroMemory(AttributeAddress, AttributeLength);
537 
538     AttributeAddress->Type = AttributeIndexRoot;
539     AttributeAddress->Length = AttributeLength;
540     AttributeAddress->NameLength = NameLength;
541     AttributeAddress->NameOffset = NameOffset;
542     AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
543 
544     AttributeAddress->Resident.ValueLength = RootLength;
545     AttributeAddress->Resident.ValueOffset = ValueOffset;
546 
547     // Set the name
548     RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR));
549 
550     // Copy the index root attribute
551     RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + ValueOffset), NewIndexRoot, RootLength);
552 
553     // move the attribute-end and file-record-end markers to the end of the file record
554     AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
555     SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
556 
557     return STATUS_SUCCESS;
558 }
559 
560 /**
561 * @name AddRun
562 * @implemented
563 *
564 * Adds a run of allocated clusters to a non-resident attribute.
565 *
566 * @param Vcb
567 * Pointer to an NTFS_VCB for the destination volume.
568 *
569 * @param AttrContext
570 * Pointer to an NTFS_ATTR_CONTEXT describing the destination attribute.
571 *
572 * @param AttrOffset
573 * Byte offset of the destination attribute relative to its file record.
574 *
575 * @param FileRecord
576 * Pointer to a complete copy of the file record containing the destination attribute. Must be at least
577 * Vcb->NtfsInfo.BytesPerFileRecord bytes long.
578 *
579 * @param NextAssignedCluster
580 * Logical cluster number of the start of the data run being added.
581 *
582 * @param RunLength
583 * How many clusters are in the data run being added. Can't be 0.
584 *
585 * @return
586 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute.
587 * STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails or if we fail to allocate a
588 * buffer for the new data runs.
589 * STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if FsRtlAddLargeMcbEntry() fails.
590 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
591 * STATUS_NOT_IMPLEMENTED if we need to migrate the attribute to an attribute list (TODO).
592 *
593 * @remarks
594 * Clusters should have been allocated previously with NtfsAllocateClusters().
595 *
596 *
597 */
598 NTSTATUS
599 AddRun(PNTFS_VCB Vcb,
600        PNTFS_ATTR_CONTEXT AttrContext,
601        ULONG AttrOffset,
602        PFILE_RECORD_HEADER FileRecord,
603        ULONGLONG NextAssignedCluster,
604        ULONG RunLength)
605 {
606     NTSTATUS Status;
607     int DataRunMaxLength;
608     PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
609     ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
610     ULONGLONG NextVBN = 0;
611 
612     PUCHAR RunBuffer;
613     ULONG RunBufferSize;
614 
615     if (!AttrContext->pRecord->IsNonResident)
616         return STATUS_INVALID_PARAMETER;
617 
618     if (AttrContext->pRecord->NonResident.AllocatedSize != 0)
619         NextVBN = AttrContext->pRecord->NonResident.HighestVCN + 1;
620 
621     // Add newly-assigned clusters to mcb
622     _SEH2_TRY
623     {
624         if (!FsRtlAddLargeMcbEntry(&AttrContext->DataRunsMCB,
625                                    NextVBN,
626                                    NextAssignedCluster,
627                                    RunLength))
628         {
629             ExRaiseStatus(STATUS_UNSUCCESSFUL);
630         }
631     }
632     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
633     {
634         DPRINT1("Failed to add LargeMcb Entry!\n");
635         _SEH2_YIELD(return _SEH2_GetExceptionCode());
636     }
637     _SEH2_END;
638 
639     RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
640     if (!RunBuffer)
641     {
642         DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
643         return STATUS_INSUFFICIENT_RESOURCES;
644     }
645 
646     // Convert the map control block back to encoded data runs
647     ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
648 
649     // Get the amount of free space between the start of the of the first data run and the attribute end
650     DataRunMaxLength = AttrContext->pRecord->Length - AttrContext->pRecord->NonResident.MappingPairsOffset;
651 
652     // Do we need to extend the attribute (or convert to attribute list)?
653     if (DataRunMaxLength < RunBufferSize)
654     {
655         PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
656         PNTFS_ATTR_RECORD NewRecord;
657 
658         // Add free space at the end of the file record to DataRunMaxLength
659         DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
660 
661         // Can we resize the attribute?
662         if (DataRunMaxLength < RunBufferSize)
663         {
664             DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d, RunBufferSize: %d\n", DataRunMaxLength, RunBufferSize);
665             ExFreePoolWithTag(RunBuffer, TAG_NTFS);
666             return STATUS_NOT_IMPLEMENTED;
667         }
668 
669         // Are there more attributes after the one we're resizing?
670         if (NextAttribute->Type != AttributeEnd)
671         {
672             PNTFS_ATTR_RECORD FinalAttribute;
673 
674             // Calculate where to move the trailing attributes
675             ULONG_PTR MoveTo = (ULONG_PTR)DestinationAttribute + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize;
676             MoveTo = ALIGN_UP_BY(MoveTo, ATTR_RECORD_ALIGNMENT);
677 
678             DPRINT1("Moving attribute(s) after this one starting with type 0x%lx\n", NextAttribute->Type);
679 
680             // Move the trailing attributes; FinalAttribute will point to the end marker
681             FinalAttribute = MoveAttributes(Vcb, NextAttribute, NextAttributeOffset, MoveTo);
682 
683             // set the file record end
684             SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END);
685         }
686 
687         // calculate position of end markers
688         NextAttributeOffset = AttrOffset + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize;
689         NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, ATTR_RECORD_ALIGNMENT);
690 
691         // Update the length of the destination attribute
692         DestinationAttribute->Length = NextAttributeOffset - AttrOffset;
693 
694         // Create a new copy of the attribute record
695         NewRecord = ExAllocatePoolWithTag(NonPagedPool, DestinationAttribute->Length, TAG_NTFS);
696         RtlCopyMemory(NewRecord, AttrContext->pRecord, AttrContext->pRecord->Length);
697         NewRecord->Length = DestinationAttribute->Length;
698 
699         // Free the old copy of the attribute record, which won't be large enough
700         ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
701 
702         // Set the attribute context's record to the new copy
703         AttrContext->pRecord = NewRecord;
704 
705         // if NextAttribute is the AttributeEnd marker
706         if (NextAttribute->Type == AttributeEnd)
707         {
708             // End the file record
709             NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
710             SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
711         }
712     }
713 
714     // Update HighestVCN
715     DestinationAttribute->NonResident.HighestVCN =
716     AttrContext->pRecord->NonResident.HighestVCN = max(NextVBN - 1 + RunLength,
717                                                      AttrContext->pRecord->NonResident.HighestVCN);
718 
719     // Write data runs to destination attribute
720     RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
721                   RunBuffer,
722                   RunBufferSize);
723 
724     // Update the attribute record in the attribute context
725     RtlCopyMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + AttrContext->pRecord->NonResident.MappingPairsOffset),
726                   RunBuffer,
727                   RunBufferSize);
728 
729     // Update the file record
730     Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
731 
732     ExFreePoolWithTag(RunBuffer, TAG_NTFS);
733 
734     NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
735 
736     return Status;
737 }
738 
739 /**
740 * @name AddStandardInformation
741 * @implemented
742 *
743 * Adds a $STANDARD_INFORMATION attribute to a given FileRecord.
744 *
745 * @param FileRecord
746 * Pointer to a complete file record to add the attribute to. Caller is responsible for
747 * ensuring FileRecord is large enough to contain $STANDARD_INFORMATION.
748 *
749 * @param AttributeAddress
750 * Pointer to the region of memory that will receive the $STANDARD_INFORMATION attribute.
751 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
752 *
753 * @return
754 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
755 * of the given file record.
756 *
757 * @remarks
758 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
759 * be of type AttributeEnd.
760 * As it's implemented, this function is only intended to assist in creating new file records. It
761 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
762 * It's the caller's responsibility to ensure the given file record has enough memory allocated
763 * for the attribute.
764 */
765 NTSTATUS
766 AddStandardInformation(PFILE_RECORD_HEADER FileRecord,
767                        PNTFS_ATTR_RECORD AttributeAddress)
768 {
769     ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
770     PSTANDARD_INFORMATION StandardInfo = (PSTANDARD_INFORMATION)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
771     LARGE_INTEGER SystemTime;
772     ULONG FileRecordEnd = AttributeAddress->Length;
773 
774     if (AttributeAddress->Type != AttributeEnd)
775     {
776         DPRINT1("FIXME: Can only add $STANDARD_INFORMATION attribute to the end of a file record.\n");
777         return STATUS_NOT_IMPLEMENTED;
778     }
779 
780     AttributeAddress->Type = AttributeStandardInformation;
781     AttributeAddress->Length = sizeof(STANDARD_INFORMATION) + ResidentHeaderLength;
782     AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
783     AttributeAddress->Resident.ValueLength = sizeof(STANDARD_INFORMATION);
784     AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
785     AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
786 
787     // set dates and times
788     KeQuerySystemTime(&SystemTime);
789     StandardInfo->CreationTime = SystemTime.QuadPart;
790     StandardInfo->ChangeTime = SystemTime.QuadPart;
791     StandardInfo->LastWriteTime = SystemTime.QuadPart;
792     StandardInfo->LastAccessTime = SystemTime.QuadPart;
793     StandardInfo->FileAttribute = NTFS_FILE_TYPE_ARCHIVE;
794 
795     // move the attribute-end and file-record-end markers to the end of the file record
796     AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
797     SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
798 
799     return STATUS_SUCCESS;
800 }
801 
802 /**
803 * @name ConvertDataRunsToLargeMCB
804 * @implemented
805 *
806 * Converts binary data runs to a map control block.
807 *
808 * @param DataRun
809 * Pointer to the run data
810 *
811 * @param DataRunsMCB
812 * Pointer to an unitialized LARGE_MCB structure.
813 *
814 * @return
815 * STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if we fail to
816 * initialize the mcb or add an entry.
817 *
818 * @remarks
819 * Initializes the LARGE_MCB pointed to by DataRunsMCB. If this function succeeds, you
820 * need to call FsRtlUninitializeLargeMcb() when you're done with DataRunsMCB. This
821 * function will ensure the LargeMCB has been unitialized in case of failure.
822 *
823 */
824 NTSTATUS
825 ConvertDataRunsToLargeMCB(PUCHAR DataRun,
826                           PLARGE_MCB DataRunsMCB,
827                           PULONGLONG pNextVBN)
828 {
829     LONGLONG  DataRunOffset;
830     ULONGLONG DataRunLength;
831     LONGLONG  DataRunStartLCN;
832     ULONGLONG LastLCN = 0;
833 
834     // Initialize the MCB, potentially catch an exception
835     _SEH2_TRY{
836         FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool);
837     } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
838         _SEH2_YIELD(return _SEH2_GetExceptionCode());
839     } _SEH2_END;
840 
841     while (*DataRun != 0)
842     {
843         DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
844 
845         if (DataRunOffset != -1)
846         {
847             // Normal data run.
848             DataRunStartLCN = LastLCN + DataRunOffset;
849             LastLCN = DataRunStartLCN;
850 
851             _SEH2_TRY{
852                 if (!FsRtlAddLargeMcbEntry(DataRunsMCB,
853                                            *pNextVBN,
854                                            DataRunStartLCN,
855                                            DataRunLength))
856                 {
857                     ExRaiseStatus(STATUS_UNSUCCESSFUL);
858                 }
859             } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
860                 FsRtlUninitializeLargeMcb(DataRunsMCB);
861                 _SEH2_YIELD(return _SEH2_GetExceptionCode());
862             } _SEH2_END;
863 
864         }
865 
866         *pNextVBN += DataRunLength;
867     }
868 
869     return STATUS_SUCCESS;
870 }
871 
872 /**
873 * @name ConvertLargeMCBToDataRuns
874 * @implemented
875 *
876 * Converts a map control block to a series of encoded data runs (used by non-resident attributes).
877 *
878 * @param DataRunsMCB
879 * Pointer to a LARGE_MCB structure describing the data runs.
880 *
881 * @param RunBuffer
882 * Pointer to the buffer that will receive the encoded data runs.
883 *
884 * @param MaxBufferSize
885 * Size of RunBuffer, in bytes.
886 *
887 * @param UsedBufferSize
888 * Pointer to a ULONG that will receive the size of the data runs in bytes. Can't be NULL.
889 *
890 * @return
891 * STATUS_SUCCESS on success, STATUS_BUFFER_TOO_SMALL if RunBuffer is too small to contain the
892 * complete output.
893 *
894 */
895 NTSTATUS
896 ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB,
897                           PUCHAR RunBuffer,
898                           ULONG MaxBufferSize,
899                           PULONG UsedBufferSize)
900 {
901     NTSTATUS Status = STATUS_SUCCESS;
902     ULONG RunBufferOffset = 0;
903     LONGLONG  DataRunOffset;
904     ULONGLONG LastLCN = 0;
905     LONGLONG Vbn, Lbn, Count;
906     ULONG i;
907 
908 
909     DPRINT("\t[Vbn, Lbn, Count]\n");
910 
911     // convert each mcb entry to a data run
912     for (i = 0; FsRtlGetNextLargeMcbEntry(DataRunsMCB, i, &Vbn, &Lbn, &Count); i++)
913     {
914         UCHAR DataRunOffsetSize = 0;
915         UCHAR DataRunLengthSize = 0;
916         UCHAR ControlByte = 0;
917 
918         // [vbn, lbn, count]
919         DPRINT("\t[%I64d, %I64d,%I64d]\n", Vbn, Lbn, Count);
920 
921         // TODO: check for holes and convert to sparse runs
922         DataRunOffset = Lbn - LastLCN;
923         LastLCN = Lbn;
924 
925         // now we need to determine how to represent DataRunOffset with the minimum number of bytes
926         DPRINT("Determining how many bytes needed to represent %I64x\n", DataRunOffset);
927         DataRunOffsetSize = GetPackedByteCount(DataRunOffset, TRUE);
928         DPRINT("%d bytes needed.\n", DataRunOffsetSize);
929 
930         // determine how to represent DataRunLengthSize with the minimum number of bytes
931         DPRINT("Determining how many bytes needed to represent %I64x\n", Count);
932         DataRunLengthSize = GetPackedByteCount(Count, TRUE);
933         DPRINT("%d bytes needed.\n", DataRunLengthSize);
934 
935         // ensure the next data run + end marker would be <= Max buffer size
936         if (RunBufferOffset + 2 + DataRunLengthSize + DataRunOffsetSize > MaxBufferSize)
937         {
938             Status = STATUS_BUFFER_TOO_SMALL;
939             DPRINT1("FIXME: Ran out of room in buffer for data runs!\n");
940             break;
941         }
942 
943         // pack and copy the control byte
944         ControlByte = (DataRunOffsetSize << 4) + DataRunLengthSize;
945         RunBuffer[RunBufferOffset++] = ControlByte;
946 
947         // copy DataRunLength
948         RtlCopyMemory(RunBuffer + RunBufferOffset, &Count, DataRunLengthSize);
949         RunBufferOffset += DataRunLengthSize;
950 
951         // copy DataRunOffset
952         RtlCopyMemory(RunBuffer + RunBufferOffset, &DataRunOffset, DataRunOffsetSize);
953         RunBufferOffset += DataRunOffsetSize;
954     }
955 
956     // End of data runs
957     RunBuffer[RunBufferOffset++] = 0;
958 
959     *UsedBufferSize = RunBufferOffset;
960     DPRINT("New Size of DataRuns: %ld\n", *UsedBufferSize);
961 
962     return Status;
963 }
964 
965 PUCHAR
966 DecodeRun(PUCHAR DataRun,
967           LONGLONG *DataRunOffset,
968           ULONGLONG *DataRunLength)
969 {
970     UCHAR DataRunOffsetSize;
971     UCHAR DataRunLengthSize;
972     CHAR i;
973 
974     DataRunOffsetSize = (*DataRun >> 4) & 0xF;
975     DataRunLengthSize = *DataRun & 0xF;
976     *DataRunOffset = 0;
977     *DataRunLength = 0;
978     DataRun++;
979     for (i = 0; i < DataRunLengthSize; i++)
980     {
981         *DataRunLength += ((ULONG64)*DataRun) << (i * 8);
982         DataRun++;
983     }
984 
985     /* NTFS 3+ sparse files */
986     if (DataRunOffsetSize == 0)
987     {
988         *DataRunOffset = -1;
989     }
990     else
991     {
992         for (i = 0; i < DataRunOffsetSize - 1; i++)
993         {
994             *DataRunOffset += ((ULONG64)*DataRun) << (i * 8);
995             DataRun++;
996         }
997         /* The last byte contains sign so we must process it different way. */
998         *DataRunOffset = ((LONG64)(CHAR)(*(DataRun++)) << (i * 8)) + *DataRunOffset;
999     }
1000 
1001     DPRINT("DataRunOffsetSize: %x\n", DataRunOffsetSize);
1002     DPRINT("DataRunLengthSize: %x\n", DataRunLengthSize);
1003     DPRINT("DataRunOffset: %x\n", *DataRunOffset);
1004     DPRINT("DataRunLength: %x\n", *DataRunLength);
1005 
1006     return DataRun;
1007 }
1008 
1009 BOOLEAN
1010 FindRun(PNTFS_ATTR_RECORD NresAttr,
1011         ULONGLONG vcn,
1012         PULONGLONG lcn,
1013         PULONGLONG count)
1014 {
1015     if (vcn < NresAttr->NonResident.LowestVCN || vcn > NresAttr->NonResident.HighestVCN)
1016         return FALSE;
1017 
1018     DecodeRun((PUCHAR)((ULONG_PTR)NresAttr + NresAttr->NonResident.MappingPairsOffset), (PLONGLONG)lcn, count);
1019 
1020     return TRUE;
1021 }
1022 
1023 /**
1024 * @name FreeClusters
1025 * @implemented
1026 *
1027 * Shrinks the allocation size of a non-resident attribute by a given number of clusters.
1028 * Frees the clusters from the volume's $BITMAP file as well as the attribute's data runs.
1029 *
1030 * @param Vcb
1031 * Pointer to an NTFS_VCB for the destination volume.
1032 *
1033 * @param AttrContext
1034 * Pointer to an NTFS_ATTR_CONTEXT describing the attribute from which the clusters will be freed.
1035 *
1036 * @param AttrOffset
1037 * Byte offset of the destination attribute relative to its file record.
1038 *
1039 * @param FileRecord
1040 * Pointer to a complete copy of the file record containing the attribute. Must be at least
1041 * Vcb->NtfsInfo.BytesPerFileRecord bytes long.
1042 *
1043 * @param ClustersToFree
1044 * Number of clusters that should be freed from the end of the data stream. Must be no more
1045 * Than the number of clusters assigned to the attribute (HighestVCN + 1).
1046 *
1047 * @return
1048 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute,
1049 * or if the caller requested more clusters be freed than the attribute has been allocated.
1050 * STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or
1051 * if ConvertDataRunsToLargeMCB() fails.
1052 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
1053 *
1054 *
1055 */
1056 NTSTATUS
1057 FreeClusters(PNTFS_VCB Vcb,
1058              PNTFS_ATTR_CONTEXT AttrContext,
1059              ULONG AttrOffset,
1060              PFILE_RECORD_HEADER FileRecord,
1061              ULONG ClustersToFree)
1062 {
1063     NTSTATUS Status = STATUS_SUCCESS;
1064     ULONG ClustersLeftToFree = ClustersToFree;
1065 
1066     PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
1067     ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
1068     PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
1069 
1070     PUCHAR RunBuffer;
1071     ULONG RunBufferSize = 0;
1072 
1073     PFILE_RECORD_HEADER BitmapRecord;
1074     PNTFS_ATTR_CONTEXT DataContext;
1075     ULONGLONG BitmapDataSize;
1076     PUCHAR BitmapData;
1077     RTL_BITMAP Bitmap;
1078     ULONG LengthWritten;
1079 
1080     if (!AttrContext->pRecord->IsNonResident)
1081     {
1082         return STATUS_INVALID_PARAMETER;
1083     }
1084 
1085     // Read the $Bitmap file
1086     BitmapRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
1087     if (BitmapRecord == NULL)
1088     {
1089         DPRINT1("Error: Unable to allocate memory for bitmap file record!\n");
1090         return STATUS_NO_MEMORY;
1091     }
1092 
1093     Status = ReadFileRecord(Vcb, NTFS_FILE_BITMAP, BitmapRecord);
1094     if (!NT_SUCCESS(Status))
1095     {
1096         DPRINT1("Error: Unable to read file record for bitmap!\n");
1097         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
1098         return 0;
1099     }
1100 
1101     Status = FindAttribute(Vcb, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL);
1102     if (!NT_SUCCESS(Status))
1103     {
1104         DPRINT1("Error: Unable to find data attribute for bitmap file!\n");
1105         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
1106         return 0;
1107     }
1108 
1109     BitmapDataSize = AttributeDataLength(DataContext->pRecord);
1110     BitmapDataSize = min(BitmapDataSize, ULONG_MAX);
1111     ASSERT((BitmapDataSize * 8) >= Vcb->NtfsInfo.ClusterCount);
1112     BitmapData = ExAllocatePoolWithTag(NonPagedPool, ROUND_UP(BitmapDataSize, Vcb->NtfsInfo.BytesPerSector), TAG_NTFS);
1113     if (BitmapData == NULL)
1114     {
1115         DPRINT1("Error: Unable to allocate memory for bitmap file data!\n");
1116         ReleaseAttributeContext(DataContext);
1117         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
1118         return 0;
1119     }
1120 
1121     ReadAttribute(Vcb, DataContext, 0, (PCHAR)BitmapData, (ULONG)BitmapDataSize);
1122 
1123     RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, Vcb->NtfsInfo.ClusterCount);
1124 
1125     // free clusters in $BITMAP file
1126     while (ClustersLeftToFree > 0)
1127     {
1128         LONGLONG LargeVbn, LargeLbn;
1129 
1130         if (!FsRtlLookupLastLargeMcbEntry(&AttrContext->DataRunsMCB, &LargeVbn, &LargeLbn))
1131         {
1132             Status = STATUS_INVALID_PARAMETER;
1133             DPRINT1("DRIVER ERROR: FreeClusters called to free %lu clusters, which is %lu more clusters than are assigned to attribute!",
1134                     ClustersToFree,
1135                     ClustersLeftToFree);
1136             break;
1137         }
1138 
1139         if (LargeLbn != -1)
1140         {
1141             // deallocate this cluster
1142             RtlClearBits(&Bitmap, LargeLbn, 1);
1143         }
1144         FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->pRecord->NonResident.HighestVCN);
1145 
1146         // decrement HighestVCN, but don't let it go below 0
1147         AttrContext->pRecord->NonResident.HighestVCN = min(AttrContext->pRecord->NonResident.HighestVCN, AttrContext->pRecord->NonResident.HighestVCN - 1);
1148         ClustersLeftToFree--;
1149     }
1150 
1151     // update $BITMAP file on disk
1152     Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, FileRecord);
1153     if (!NT_SUCCESS(Status))
1154     {
1155         ReleaseAttributeContext(DataContext);
1156         ExFreePoolWithTag(BitmapData, TAG_NTFS);
1157         ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
1158         return Status;
1159     }
1160 
1161     ReleaseAttributeContext(DataContext);
1162     ExFreePoolWithTag(BitmapData, TAG_NTFS);
1163     ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
1164 
1165     // Save updated data runs to file record
1166 
1167     // Allocate some memory for a new RunBuffer
1168     RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1169     if (!RunBuffer)
1170     {
1171         DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
1172         return STATUS_INSUFFICIENT_RESOURCES;
1173     }
1174 
1175     // Convert the map control block back to encoded data runs
1176     ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
1177 
1178     // Update HighestVCN
1179     DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN;
1180 
1181     // Write data runs to destination attribute
1182     RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
1183                   RunBuffer,
1184                   RunBufferSize);
1185 
1186     // Is DestinationAttribute the last attribute in the file record?
1187     if (NextAttribute->Type == AttributeEnd)
1188     {
1189         // update attribute length
1190         DestinationAttribute->Length = ALIGN_UP_BY(AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize,
1191                                                  ATTR_RECORD_ALIGNMENT);
1192 
1193         ASSERT(DestinationAttribute->Length <= AttrContext->pRecord->Length);
1194 
1195         AttrContext->pRecord->Length = DestinationAttribute->Length;
1196 
1197         // write end markers
1198         NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
1199         SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
1200     }
1201 
1202     // Update the file record
1203     Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
1204 
1205     ExFreePoolWithTag(RunBuffer, TAG_NTFS);
1206 
1207     NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
1208 
1209     return Status;
1210 }
1211 
1212 static
1213 NTSTATUS
1214 InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context)
1215 {
1216     ULONGLONG ListSize;
1217     PNTFS_ATTR_RECORD Attribute;
1218     PNTFS_ATTR_CONTEXT ListContext;
1219 
1220     DPRINT("InternalReadNonResidentAttributes(%p)\n", Context);
1221 
1222     Attribute = Context->CurrAttr;
1223     ASSERT(Attribute->Type == AttributeAttributeList);
1224 
1225     if (Context->OnlyResident)
1226     {
1227         Context->NonResidentStart = NULL;
1228         Context->NonResidentEnd = NULL;
1229         return STATUS_SUCCESS;
1230     }
1231 
1232     if (Context->NonResidentStart != NULL)
1233     {
1234         return STATUS_FILE_CORRUPT_ERROR;
1235     }
1236 
1237     ListContext = PrepareAttributeContext(Attribute);
1238     ListSize = AttributeDataLength(ListContext->pRecord);
1239     if (ListSize > 0xFFFFFFFF)
1240     {
1241         ReleaseAttributeContext(ListContext);
1242         return STATUS_BUFFER_OVERFLOW;
1243     }
1244 
1245     Context->NonResidentStart = ExAllocatePoolWithTag(NonPagedPool, (ULONG)ListSize, TAG_NTFS);
1246     if (Context->NonResidentStart == NULL)
1247     {
1248         ReleaseAttributeContext(ListContext);
1249         return STATUS_INSUFFICIENT_RESOURCES;
1250     }
1251 
1252     if (ReadAttribute(Context->Vcb, ListContext, 0, (PCHAR)Context->NonResidentStart, (ULONG)ListSize) != ListSize)
1253     {
1254         ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
1255         Context->NonResidentStart = NULL;
1256         ReleaseAttributeContext(ListContext);
1257         return STATUS_FILE_CORRUPT_ERROR;
1258     }
1259 
1260     ReleaseAttributeContext(ListContext);
1261     Context->NonResidentEnd = (PNTFS_ATTR_RECORD)((PCHAR)Context->NonResidentStart + ListSize);
1262     return STATUS_SUCCESS;
1263 }
1264 
1265 static
1266 PNTFS_ATTR_RECORD
1267 InternalGetNextAttribute(PFIND_ATTR_CONTXT Context)
1268 {
1269     PNTFS_ATTR_RECORD NextAttribute;
1270 
1271     if (Context->CurrAttr == (PVOID)-1)
1272     {
1273         return NULL;
1274     }
1275 
1276     if (Context->CurrAttr >= Context->FirstAttr &&
1277         Context->CurrAttr < Context->LastAttr)
1278     {
1279         if (Context->CurrAttr->Length == 0)
1280         {
1281             DPRINT1("Broken length!\n");
1282             Context->CurrAttr = (PVOID)-1;
1283             return NULL;
1284         }
1285 
1286         NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
1287 
1288         if (NextAttribute > Context->LastAttr || NextAttribute < Context->FirstAttr)
1289         {
1290             DPRINT1("Broken length: 0x%lx!\n", Context->CurrAttr->Length);
1291             Context->CurrAttr = (PVOID)-1;
1292             return NULL;
1293         }
1294 
1295         Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
1296         Context->CurrAttr = NextAttribute;
1297 
1298         if (Context->CurrAttr < Context->LastAttr &&
1299             Context->CurrAttr->Type != AttributeEnd)
1300         {
1301             return Context->CurrAttr;
1302         }
1303     }
1304 
1305     if (Context->NonResidentStart == NULL)
1306     {
1307         Context->CurrAttr = (PVOID)-1;
1308         return NULL;
1309     }
1310 
1311     if (Context->CurrAttr < Context->NonResidentStart ||
1312         Context->CurrAttr >= Context->NonResidentEnd)
1313     {
1314         Context->CurrAttr = Context->NonResidentStart;
1315     }
1316     else if (Context->CurrAttr->Length != 0)
1317     {
1318         NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
1319         Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
1320         Context->CurrAttr = NextAttribute;
1321     }
1322     else
1323     {
1324         DPRINT1("Broken length!\n");
1325         Context->CurrAttr = (PVOID)-1;
1326         return NULL;
1327     }
1328 
1329     if (Context->CurrAttr < Context->NonResidentEnd &&
1330         Context->CurrAttr->Type != AttributeEnd)
1331     {
1332         return Context->CurrAttr;
1333     }
1334 
1335     Context->CurrAttr = (PVOID)-1;
1336     return NULL;
1337 }
1338 
1339 NTSTATUS
1340 FindFirstAttribute(PFIND_ATTR_CONTXT Context,
1341                    PDEVICE_EXTENSION Vcb,
1342                    PFILE_RECORD_HEADER FileRecord,
1343                    BOOLEAN OnlyResident,
1344                    PNTFS_ATTR_RECORD * Attribute)
1345 {
1346     NTSTATUS Status;
1347 
1348     DPRINT("FindFistAttribute(%p, %p, %p, %p, %u, %p)\n", Context, Vcb, FileRecord, OnlyResident, Attribute);
1349 
1350     Context->Vcb = Vcb;
1351     Context->OnlyResident = OnlyResident;
1352     Context->FirstAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
1353     Context->CurrAttr = Context->FirstAttr;
1354     Context->LastAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse);
1355     Context->NonResidentStart = NULL;
1356     Context->NonResidentEnd = NULL;
1357     Context->Offset = FileRecord->AttributeOffset;
1358 
1359     if (Context->FirstAttr->Type == AttributeEnd)
1360     {
1361         Context->CurrAttr = (PVOID)-1;
1362         return STATUS_END_OF_FILE;
1363     }
1364     else if (Context->FirstAttr->Type == AttributeAttributeList)
1365     {
1366         Status = InternalReadNonResidentAttributes(Context);
1367         if (!NT_SUCCESS(Status))
1368         {
1369             return Status;
1370         }
1371 
1372         *Attribute = InternalGetNextAttribute(Context);
1373         if (*Attribute == NULL)
1374         {
1375             return STATUS_END_OF_FILE;
1376         }
1377     }
1378     else
1379     {
1380         *Attribute = Context->CurrAttr;
1381         Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord;
1382     }
1383 
1384     return STATUS_SUCCESS;
1385 }
1386 
1387 NTSTATUS
1388 FindNextAttribute(PFIND_ATTR_CONTXT Context,
1389                   PNTFS_ATTR_RECORD * Attribute)
1390 {
1391     NTSTATUS Status;
1392 
1393     DPRINT("FindNextAttribute(%p, %p)\n", Context, Attribute);
1394 
1395     *Attribute = InternalGetNextAttribute(Context);
1396     if (*Attribute == NULL)
1397     {
1398         return STATUS_END_OF_FILE;
1399     }
1400 
1401     if (Context->CurrAttr->Type != AttributeAttributeList)
1402     {
1403         return STATUS_SUCCESS;
1404     }
1405 
1406     Status = InternalReadNonResidentAttributes(Context);
1407     if (!NT_SUCCESS(Status))
1408     {
1409         return Status;
1410     }
1411 
1412     *Attribute = InternalGetNextAttribute(Context);
1413     if (*Attribute == NULL)
1414     {
1415         return STATUS_END_OF_FILE;
1416     }
1417 
1418     return STATUS_SUCCESS;
1419 }
1420 
1421 VOID
1422 FindCloseAttribute(PFIND_ATTR_CONTXT Context)
1423 {
1424     if (Context->NonResidentStart != NULL)
1425     {
1426         ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
1427         Context->NonResidentStart = NULL;
1428     }
1429 }
1430 
1431 static
1432 VOID
1433 NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute)
1434 {
1435     PFILENAME_ATTRIBUTE FileNameAttr;
1436 
1437     DbgPrint("  $FILE_NAME ");
1438 
1439 //    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1440 
1441     FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1442     DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name);
1443     DbgPrint(" '%x' \n", FileNameAttr->FileAttributes);
1444     DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize);
1445     DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber);
1446 }
1447 
1448 
1449 static
1450 VOID
1451 NtfsDumpStandardInformationAttribute(PNTFS_ATTR_RECORD Attribute)
1452 {
1453     PSTANDARD_INFORMATION StandardInfoAttr;
1454 
1455     DbgPrint("  $STANDARD_INFORMATION ");
1456 
1457 //    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1458 
1459     StandardInfoAttr = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1460     DbgPrint(" '%x' ", StandardInfoAttr->FileAttribute);
1461 }
1462 
1463 
1464 static
1465 VOID
1466 NtfsDumpVolumeNameAttribute(PNTFS_ATTR_RECORD Attribute)
1467 {
1468     PWCHAR VolumeName;
1469 
1470     DbgPrint("  $VOLUME_NAME ");
1471 
1472 //    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1473 
1474     VolumeName = (PWCHAR)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1475     DbgPrint(" '%.*S' ", Attribute->Resident.ValueLength / sizeof(WCHAR), VolumeName);
1476 }
1477 
1478 
1479 static
1480 VOID
1481 NtfsDumpVolumeInformationAttribute(PNTFS_ATTR_RECORD Attribute)
1482 {
1483     PVOLINFO_ATTRIBUTE VolInfoAttr;
1484 
1485     DbgPrint("  $VOLUME_INFORMATION ");
1486 
1487 //    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1488 
1489     VolInfoAttr = (PVOLINFO_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1490     DbgPrint(" NTFS Version %u.%u  Flags 0x%04hx ",
1491              VolInfoAttr->MajorVersion,
1492              VolInfoAttr->MinorVersion,
1493              VolInfoAttr->Flags);
1494 }
1495 
1496 
1497 static
1498 VOID
1499 NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute)
1500 {
1501     PINDEX_ROOT_ATTRIBUTE IndexRootAttr;
1502     ULONG CurrentOffset;
1503     ULONG CurrentNode;
1504 
1505     IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1506 
1507     if (IndexRootAttr->AttributeType == AttributeFileName)
1508         ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME);
1509 
1510     DbgPrint("  $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
1511 
1512     if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL)
1513     {
1514         DbgPrint(" (small)\n");
1515     }
1516     else
1517     {
1518         ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE);
1519         DbgPrint(" (large)\n");
1520     }
1521 
1522     DbgPrint("   Offset to first index: 0x%lx\n   Total size of index entries: 0x%lx\n   Allocated size of node: 0x%lx\n",
1523              IndexRootAttr->Header.FirstEntryOffset,
1524              IndexRootAttr->Header.TotalSizeOfEntries,
1525              IndexRootAttr->Header.AllocatedSize);
1526     CurrentOffset = IndexRootAttr->Header.FirstEntryOffset;
1527     CurrentNode = 0;
1528     // print details of every node in the index
1529     while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries)
1530     {
1531         PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + CurrentOffset);
1532         DbgPrint("   Index Node Entry %lu", CurrentNode++);
1533         if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_NODE))
1534             DbgPrint(" (Branch)");
1535         else
1536             DbgPrint(" (Leaf)");
1537         if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END))
1538         {
1539             DbgPrint(" (Dummy Key)");
1540         }
1541         DbgPrint("\n    File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile);
1542         DbgPrint("    Index Entry Length: 0x%x\n", currentIndexExtry->Length);
1543         DbgPrint("    Index Key Length: 0x%x\n", currentIndexExtry->KeyLength);
1544 
1545         // if this isn't the final (dummy) node, print info about the key (Filename attribute)
1546         if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END))
1547         {
1548             UNICODE_STRING Name;
1549             DbgPrint("     Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber);
1550             DbgPrint("     $FILENAME indexed: ");
1551             Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR);
1552             Name.MaximumLength = Name.Length;
1553             Name.Buffer = currentIndexExtry->FileName.Name;
1554             DbgPrint("'%wZ'\n", &Name);
1555         }
1556 
1557         // if this node has a sub-node beneath it
1558         if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE)
1559         {
1560             // Print the VCN of the sub-node
1561             PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG));
1562             DbgPrint("    VCN of sub-node: 0x%llx\n", *SubNodeVCN);
1563         }
1564 
1565         CurrentOffset += currentIndexExtry->Length;
1566         ASSERT(currentIndexExtry->Length);
1567     }
1568 
1569 }
1570 
1571 
1572 static
1573 VOID
1574 NtfsDumpAttribute(PDEVICE_EXTENSION Vcb,
1575                   PNTFS_ATTR_RECORD Attribute)
1576 {
1577     UNICODE_STRING Name;
1578 
1579     ULONGLONG lcn = 0;
1580     ULONGLONG runcount = 0;
1581 
1582     switch (Attribute->Type)
1583     {
1584         case AttributeFileName:
1585             NtfsDumpFileNameAttribute(Attribute);
1586             break;
1587 
1588         case AttributeStandardInformation:
1589             NtfsDumpStandardInformationAttribute(Attribute);
1590             break;
1591 
1592         case AttributeObjectId:
1593             DbgPrint("  $OBJECT_ID ");
1594             break;
1595 
1596         case AttributeSecurityDescriptor:
1597             DbgPrint("  $SECURITY_DESCRIPTOR ");
1598             break;
1599 
1600         case AttributeVolumeName:
1601             NtfsDumpVolumeNameAttribute(Attribute);
1602             break;
1603 
1604         case AttributeVolumeInformation:
1605             NtfsDumpVolumeInformationAttribute(Attribute);
1606             break;
1607 
1608         case AttributeData:
1609             DbgPrint("  $DATA ");
1610             //DataBuf = ExAllocatePool(NonPagedPool,AttributeLengthAllocated(Attribute));
1611             break;
1612 
1613         case AttributeIndexRoot:
1614             NtfsDumpIndexRootAttribute(Attribute);
1615             break;
1616 
1617         case AttributeIndexAllocation:
1618             DbgPrint("  $INDEX_ALLOCATION ");
1619             break;
1620 
1621         case AttributeBitmap:
1622             DbgPrint("  $BITMAP ");
1623             break;
1624 
1625         case AttributeReparsePoint:
1626             DbgPrint("  $REPARSE_POINT ");
1627             break;
1628 
1629         case AttributeEAInformation:
1630             DbgPrint("  $EA_INFORMATION ");
1631             break;
1632 
1633         case AttributeEA:
1634             DbgPrint("  $EA ");
1635             break;
1636 
1637         case AttributePropertySet:
1638             DbgPrint("  $PROPERTY_SET ");
1639             break;
1640 
1641         case AttributeLoggedUtilityStream:
1642             DbgPrint("  $LOGGED_UTILITY_STREAM ");
1643             break;
1644 
1645         default:
1646             DbgPrint("  Attribute %lx ",
1647                      Attribute->Type);
1648             break;
1649     }
1650 
1651     if (Attribute->Type != AttributeAttributeList)
1652     {
1653         if (Attribute->NameLength != 0)
1654         {
1655             Name.Length = Attribute->NameLength * sizeof(WCHAR);
1656             Name.MaximumLength = Name.Length;
1657             Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset);
1658 
1659             DbgPrint("'%wZ' ", &Name);
1660         }
1661 
1662         DbgPrint("(%s)\n",
1663                  Attribute->IsNonResident ? "non-resident" : "resident");
1664 
1665         if (Attribute->IsNonResident)
1666         {
1667             FindRun(Attribute,0,&lcn, &runcount);
1668 
1669             DbgPrint("  AllocatedSize %I64u  DataSize %I64u InitilizedSize %I64u\n",
1670                      Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize, Attribute->NonResident.InitializedSize);
1671             DbgPrint("  logical clusters: %I64u - %I64u\n",
1672                      lcn, lcn + runcount - 1);
1673         }
1674         else
1675             DbgPrint("    %u bytes of data\n", Attribute->Resident.ValueLength);
1676     }
1677 }
1678 
1679 
1680 VOID NtfsDumpDataRunData(PUCHAR DataRun)
1681 {
1682     UCHAR DataRunOffsetSize;
1683     UCHAR DataRunLengthSize;
1684     CHAR i;
1685 
1686     DbgPrint("%02x ", *DataRun);
1687 
1688     if (*DataRun == 0)
1689         return;
1690 
1691     DataRunOffsetSize = (*DataRun >> 4) & 0xF;
1692     DataRunLengthSize = *DataRun & 0xF;
1693 
1694     DataRun++;
1695     for (i = 0; i < DataRunLengthSize; i++)
1696     {
1697         DbgPrint("%02x ", *DataRun);
1698         DataRun++;
1699     }
1700 
1701     for (i = 0; i < DataRunOffsetSize; i++)
1702     {
1703         DbgPrint("%02x ", *DataRun);
1704         DataRun++;
1705     }
1706 
1707     NtfsDumpDataRunData(DataRun);
1708 }
1709 
1710 
1711 VOID
1712 NtfsDumpDataRuns(PVOID StartOfRun,
1713                  ULONGLONG CurrentLCN)
1714 {
1715     PUCHAR DataRun = StartOfRun;
1716     LONGLONG DataRunOffset;
1717     ULONGLONG DataRunLength;
1718 
1719     if (CurrentLCN == 0)
1720     {
1721         DPRINT1("Dumping data runs.\n\tData:\n\t\t");
1722         NtfsDumpDataRunData(StartOfRun);
1723         DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n");
1724     }
1725 
1726     DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1727 
1728     if (DataRunOffset != -1)
1729         CurrentLCN += DataRunOffset;
1730 
1731     DbgPrint("\t\t%I64d\t", DataRunOffset);
1732     if (DataRunOffset < 99999)
1733         DbgPrint("\t");
1734     DbgPrint("%I64u\t", CurrentLCN);
1735     if (CurrentLCN < 99999)
1736         DbgPrint("\t");
1737     DbgPrint("%I64u\n", DataRunLength);
1738 
1739     if (*DataRun == 0)
1740         DbgPrint("\t\t00\n");
1741     else
1742         NtfsDumpDataRuns(DataRun, CurrentLCN);
1743 }
1744 
1745 
1746 VOID
1747 NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb,
1748                        PFILE_RECORD_HEADER FileRecord)
1749 {
1750     NTSTATUS Status;
1751     FIND_ATTR_CONTXT Context;
1752     PNTFS_ATTR_RECORD Attribute;
1753 
1754     Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1755     while (NT_SUCCESS(Status))
1756     {
1757         NtfsDumpAttribute(Vcb, Attribute);
1758 
1759         Status = FindNextAttribute(&Context, &Attribute);
1760     }
1761 
1762     FindCloseAttribute(&Context);
1763 }
1764 
1765 PFILENAME_ATTRIBUTE
1766 GetFileNameFromRecord(PDEVICE_EXTENSION Vcb,
1767                       PFILE_RECORD_HEADER FileRecord,
1768                       UCHAR NameType)
1769 {
1770     FIND_ATTR_CONTXT Context;
1771     PNTFS_ATTR_RECORD Attribute;
1772     PFILENAME_ATTRIBUTE Name;
1773     NTSTATUS Status;
1774 
1775     Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1776     while (NT_SUCCESS(Status))
1777     {
1778         if (Attribute->Type == AttributeFileName)
1779         {
1780             Name = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1781             if (Name->NameType == NameType ||
1782                 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_WIN32) ||
1783                 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_DOS))
1784             {
1785                 FindCloseAttribute(&Context);
1786                 return Name;
1787             }
1788         }
1789 
1790         Status = FindNextAttribute(&Context, &Attribute);
1791     }
1792 
1793     FindCloseAttribute(&Context);
1794     return NULL;
1795 }
1796 
1797 /**
1798 * GetPackedByteCount
1799 * Returns the minimum number of bytes needed to represent the value of a
1800 * 64-bit number. Used to encode data runs.
1801 */
1802 UCHAR
1803 GetPackedByteCount(LONGLONG NumberToPack,
1804                    BOOLEAN IsSigned)
1805 {
1806     if (!IsSigned)
1807     {
1808         if (NumberToPack >= 0x0100000000000000)
1809             return 8;
1810         if (NumberToPack >= 0x0001000000000000)
1811             return 7;
1812         if (NumberToPack >= 0x0000010000000000)
1813             return 6;
1814         if (NumberToPack >= 0x0000000100000000)
1815             return 5;
1816         if (NumberToPack >= 0x0000000001000000)
1817             return 4;
1818         if (NumberToPack >= 0x0000000000010000)
1819             return 3;
1820         if (NumberToPack >= 0x0000000000000100)
1821             return 2;
1822         return 1;
1823     }
1824 
1825     if (NumberToPack > 0)
1826     {
1827         // we have to make sure the number that gets encoded won't be interpreted as negative
1828         if (NumberToPack >= 0x0080000000000000)
1829             return 8;
1830         if (NumberToPack >= 0x0000800000000000)
1831             return 7;
1832         if (NumberToPack >= 0x0000008000000000)
1833             return 6;
1834         if (NumberToPack >= 0x0000000080000000)
1835             return 5;
1836         if (NumberToPack >= 0x0000000000800000)
1837             return 4;
1838         if (NumberToPack >= 0x0000000000008000)
1839             return 3;
1840         if (NumberToPack >= 0x0000000000000080)
1841             return 2;
1842     }
1843     else
1844     {
1845         // negative number
1846         if (NumberToPack <= 0xff80000000000000)
1847             return 8;
1848         if (NumberToPack <= 0xffff800000000000)
1849             return 7;
1850         if (NumberToPack <= 0xffffff8000000000)
1851             return 6;
1852         if (NumberToPack <= 0xffffffff80000000)
1853             return 5;
1854         if (NumberToPack <= 0xffffffffff800000)
1855             return 4;
1856         if (NumberToPack <= 0xffffffffffff8000)
1857             return 3;
1858         if (NumberToPack <= 0xffffffffffffff80)
1859             return 2;
1860     }
1861     return 1;
1862 }
1863 
1864 NTSTATUS
1865 GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster)
1866 {
1867     LONGLONG DataRunOffset;
1868     ULONGLONG DataRunLength;
1869     LONGLONG DataRunStartLCN;
1870 
1871     ULONGLONG LastLCN = 0;
1872     PUCHAR DataRun = (PUCHAR)Attribute + Attribute->NonResident.MappingPairsOffset;
1873 
1874     if (!Attribute->IsNonResident)
1875         return STATUS_INVALID_PARAMETER;
1876 
1877     while (1)
1878     {
1879         DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1880 
1881         if (DataRunOffset != -1)
1882         {
1883             // Normal data run.
1884             DataRunStartLCN = LastLCN + DataRunOffset;
1885             LastLCN = DataRunStartLCN;
1886             *LastCluster = LastLCN + DataRunLength - 1;
1887         }
1888 
1889         if (*DataRun == 0)
1890             break;
1891     }
1892 
1893     return STATUS_SUCCESS;
1894 }
1895 
1896 PSTANDARD_INFORMATION
1897 GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb,
1898                                  PFILE_RECORD_HEADER FileRecord)
1899 {
1900     NTSTATUS Status;
1901     FIND_ATTR_CONTXT Context;
1902     PNTFS_ATTR_RECORD Attribute;
1903     PSTANDARD_INFORMATION StdInfo;
1904 
1905     Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1906     while (NT_SUCCESS(Status))
1907     {
1908         if (Attribute->Type == AttributeStandardInformation)
1909         {
1910             StdInfo = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1911             FindCloseAttribute(&Context);
1912             return StdInfo;
1913         }
1914 
1915         Status = FindNextAttribute(&Context, &Attribute);
1916     }
1917 
1918     FindCloseAttribute(&Context);
1919     return NULL;
1920 }
1921 
1922 /**
1923 * @name GetFileNameAttributeLength
1924 * @implemented
1925 *
1926 * Returns the size of a given FILENAME_ATTRIBUTE, in bytes.
1927 *
1928 * @param FileNameAttribute
1929 * Pointer to a FILENAME_ATTRIBUTE to determine the size of.
1930 *
1931 * @remarks
1932 * The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end.
1933 * This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD.
1934 */
1935 ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute)
1936 {
1937     ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR));
1938     return Length;
1939 }
1940 
1941 PFILENAME_ATTRIBUTE
1942 GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb,
1943                           PFILE_RECORD_HEADER FileRecord)
1944 {
1945     PFILENAME_ATTRIBUTE FileName;
1946 
1947     FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_POSIX);
1948     if (FileName == NULL)
1949     {
1950         FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_WIN32);
1951         if (FileName == NULL)
1952         {
1953             FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_DOS);
1954         }
1955     }
1956 
1957     return FileName;
1958 }
1959 
1960 /* EOF */
1961