xref: /reactos/drivers/filesystems/ntfs/attrib.c (revision cdf90707)
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_ATTRIBUTE_LIST_ITEM)((PCHAR)Context->NonResidentStart + ListSize);
1262     return STATUS_SUCCESS;
1263 }
1264 
1265 static
1266 PNTFS_ATTRIBUTE_LIST_ITEM
1267 InternalGetNextAttributeListItem(PFIND_ATTR_CONTXT Context)
1268 {
1269     PNTFS_ATTRIBUTE_LIST_ITEM NextItem;
1270 
1271     if (Context->NonResidentCur == (PVOID)-1)
1272     {
1273         return NULL;
1274     }
1275 
1276     if (Context->NonResidentCur == NULL || Context->NonResidentCur->Type == AttributeEnd)
1277     {
1278         Context->NonResidentCur = (PVOID)-1;
1279         return NULL;
1280     }
1281 
1282     if (Context->NonResidentCur->Length == 0)
1283     {
1284         DPRINT1("Broken length list entry length !");
1285         Context->NonResidentCur = (PVOID)-1;
1286         return NULL;
1287     }
1288 
1289     NextItem = (PNTFS_ATTRIBUTE_LIST_ITEM)((PCHAR)Context->NonResidentCur + Context->NonResidentCur->Length);
1290     if (NextItem->Length == 0 || NextItem->Type == AttributeEnd)
1291     {
1292         Context->NonResidentCur = (PVOID)-1;
1293         return NULL;
1294     }
1295 
1296     if (NextItem < Context->NonResidentStart || NextItem > Context->NonResidentEnd)
1297     {
1298         Context->NonResidentCur = (PVOID)-1;
1299         return NULL;
1300     }
1301 
1302     Context->NonResidentCur = NextItem;
1303     return NextItem;
1304 }
1305 
1306 NTSTATUS
1307 FindFirstAttributeListItem(PFIND_ATTR_CONTXT Context,
1308                            PNTFS_ATTRIBUTE_LIST_ITEM *Item)
1309 {
1310     if (Context->NonResidentStart == NULL || Context->NonResidentStart->Type == AttributeEnd)
1311     {
1312         return STATUS_UNSUCCESSFUL;
1313     }
1314 
1315     Context->NonResidentCur = Context->NonResidentStart;
1316     *Item = Context->NonResidentCur;
1317     return STATUS_SUCCESS;
1318 }
1319 
1320 NTSTATUS
1321 FindNextAttributeListItem(PFIND_ATTR_CONTXT Context,
1322                           PNTFS_ATTRIBUTE_LIST_ITEM *Item)
1323 {
1324     *Item = InternalGetNextAttributeListItem(Context);
1325     if (*Item == NULL)
1326     {
1327         return STATUS_UNSUCCESSFUL;
1328     }
1329     return STATUS_SUCCESS;
1330 }
1331 
1332 static
1333 PNTFS_ATTR_RECORD
1334 InternalGetNextAttribute(PFIND_ATTR_CONTXT Context)
1335 {
1336     PNTFS_ATTR_RECORD NextAttribute;
1337 
1338     if (Context->CurrAttr == (PVOID)-1)
1339     {
1340         return NULL;
1341     }
1342 
1343     if (Context->CurrAttr >= Context->FirstAttr &&
1344         Context->CurrAttr < Context->LastAttr)
1345     {
1346         if (Context->CurrAttr->Length == 0)
1347         {
1348             DPRINT1("Broken length!\n");
1349             Context->CurrAttr = (PVOID)-1;
1350             return NULL;
1351         }
1352 
1353         NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
1354 
1355         if (NextAttribute > Context->LastAttr || NextAttribute < Context->FirstAttr)
1356         {
1357             DPRINT1("Broken length: 0x%lx!\n", Context->CurrAttr->Length);
1358             Context->CurrAttr = (PVOID)-1;
1359             return NULL;
1360         }
1361 
1362         Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
1363         Context->CurrAttr = NextAttribute;
1364 
1365         if (Context->CurrAttr < Context->LastAttr &&
1366             Context->CurrAttr->Type != AttributeEnd)
1367         {
1368             return Context->CurrAttr;
1369         }
1370     }
1371 
1372     if (Context->NonResidentStart == NULL)
1373     {
1374         Context->CurrAttr = (PVOID)-1;
1375         return NULL;
1376     }
1377 
1378     Context->CurrAttr = (PVOID)-1;
1379     return NULL;
1380 }
1381 
1382 NTSTATUS
1383 FindFirstAttribute(PFIND_ATTR_CONTXT Context,
1384                    PDEVICE_EXTENSION Vcb,
1385                    PFILE_RECORD_HEADER FileRecord,
1386                    BOOLEAN OnlyResident,
1387                    PNTFS_ATTR_RECORD * Attribute)
1388 {
1389     NTSTATUS Status;
1390 
1391     DPRINT("FindFistAttribute(%p, %p, %p, %p, %u, %p)\n", Context, Vcb, FileRecord, OnlyResident, Attribute);
1392 
1393     Context->Vcb = Vcb;
1394     Context->OnlyResident = OnlyResident;
1395     Context->FirstAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
1396     Context->CurrAttr = Context->FirstAttr;
1397     Context->LastAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse);
1398     Context->NonResidentStart = NULL;
1399     Context->NonResidentEnd = NULL;
1400     Context->Offset = FileRecord->AttributeOffset;
1401 
1402     if (Context->FirstAttr->Type == AttributeEnd)
1403     {
1404         Context->CurrAttr = (PVOID)-1;
1405         return STATUS_END_OF_FILE;
1406     }
1407     else if (Context->FirstAttr->Type == AttributeAttributeList)
1408     {
1409         Status = InternalReadNonResidentAttributes(Context);
1410         if (!NT_SUCCESS(Status))
1411         {
1412             return Status;
1413         }
1414 
1415         *Attribute = InternalGetNextAttribute(Context);
1416         if (*Attribute == NULL)
1417         {
1418             return STATUS_END_OF_FILE;
1419         }
1420     }
1421     else
1422     {
1423         *Attribute = Context->CurrAttr;
1424         Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord;
1425     }
1426 
1427     return STATUS_SUCCESS;
1428 }
1429 
1430 NTSTATUS
1431 FindNextAttribute(PFIND_ATTR_CONTXT Context,
1432                   PNTFS_ATTR_RECORD * Attribute)
1433 {
1434     NTSTATUS Status;
1435 
1436     DPRINT("FindNextAttribute(%p, %p)\n", Context, Attribute);
1437 
1438     *Attribute = InternalGetNextAttribute(Context);
1439     if (*Attribute == NULL)
1440     {
1441         return STATUS_END_OF_FILE;
1442     }
1443 
1444     if (Context->CurrAttr->Type != AttributeAttributeList)
1445     {
1446         return STATUS_SUCCESS;
1447     }
1448 
1449     Status = InternalReadNonResidentAttributes(Context);
1450     if (!NT_SUCCESS(Status))
1451     {
1452         return Status;
1453     }
1454 
1455     *Attribute = InternalGetNextAttribute(Context);
1456     if (*Attribute == NULL)
1457     {
1458         return STATUS_END_OF_FILE;
1459     }
1460 
1461     return STATUS_SUCCESS;
1462 }
1463 
1464 VOID
1465 FindCloseAttribute(PFIND_ATTR_CONTXT Context)
1466 {
1467     if (Context->NonResidentStart != NULL)
1468     {
1469         ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
1470         Context->NonResidentStart = NULL;
1471     }
1472 }
1473 
1474 static
1475 VOID
1476 NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute)
1477 {
1478     PFILENAME_ATTRIBUTE FileNameAttr;
1479 
1480     DbgPrint("  $FILE_NAME ");
1481 
1482 //    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1483 
1484     FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1485     DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name);
1486     DbgPrint(" '%x' \n", FileNameAttr->FileAttributes);
1487     DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize);
1488     DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber);
1489 }
1490 
1491 
1492 static
1493 VOID
1494 NtfsDumpStandardInformationAttribute(PNTFS_ATTR_RECORD Attribute)
1495 {
1496     PSTANDARD_INFORMATION StandardInfoAttr;
1497 
1498     DbgPrint("  $STANDARD_INFORMATION ");
1499 
1500 //    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1501 
1502     StandardInfoAttr = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1503     DbgPrint(" '%x' ", StandardInfoAttr->FileAttribute);
1504 }
1505 
1506 
1507 static
1508 VOID
1509 NtfsDumpVolumeNameAttribute(PNTFS_ATTR_RECORD Attribute)
1510 {
1511     PWCHAR VolumeName;
1512 
1513     DbgPrint("  $VOLUME_NAME ");
1514 
1515 //    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1516 
1517     VolumeName = (PWCHAR)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1518     DbgPrint(" '%.*S' ", Attribute->Resident.ValueLength / sizeof(WCHAR), VolumeName);
1519 }
1520 
1521 
1522 static
1523 VOID
1524 NtfsDumpVolumeInformationAttribute(PNTFS_ATTR_RECORD Attribute)
1525 {
1526     PVOLINFO_ATTRIBUTE VolInfoAttr;
1527 
1528     DbgPrint("  $VOLUME_INFORMATION ");
1529 
1530 //    DbgPrint(" Length %lu  Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1531 
1532     VolInfoAttr = (PVOLINFO_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1533     DbgPrint(" NTFS Version %u.%u  Flags 0x%04hx ",
1534              VolInfoAttr->MajorVersion,
1535              VolInfoAttr->MinorVersion,
1536              VolInfoAttr->Flags);
1537 }
1538 
1539 
1540 static
1541 VOID
1542 NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute)
1543 {
1544     PINDEX_ROOT_ATTRIBUTE IndexRootAttr;
1545     ULONG CurrentOffset;
1546     ULONG CurrentNode;
1547 
1548     IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1549 
1550     if (IndexRootAttr->AttributeType == AttributeFileName)
1551         ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME);
1552 
1553     DbgPrint("  $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
1554 
1555     if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL)
1556     {
1557         DbgPrint(" (small)\n");
1558     }
1559     else
1560     {
1561         ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE);
1562         DbgPrint(" (large)\n");
1563     }
1564 
1565     DbgPrint("   Offset to first index: 0x%lx\n   Total size of index entries: 0x%lx\n   Allocated size of node: 0x%lx\n",
1566              IndexRootAttr->Header.FirstEntryOffset,
1567              IndexRootAttr->Header.TotalSizeOfEntries,
1568              IndexRootAttr->Header.AllocatedSize);
1569     CurrentOffset = IndexRootAttr->Header.FirstEntryOffset;
1570     CurrentNode = 0;
1571     // print details of every node in the index
1572     while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries)
1573     {
1574         PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + CurrentOffset);
1575         DbgPrint("   Index Node Entry %lu", CurrentNode++);
1576         if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_NODE))
1577             DbgPrint(" (Branch)");
1578         else
1579             DbgPrint(" (Leaf)");
1580         if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END))
1581         {
1582             DbgPrint(" (Dummy Key)");
1583         }
1584         DbgPrint("\n    File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile);
1585         DbgPrint("    Index Entry Length: 0x%x\n", currentIndexExtry->Length);
1586         DbgPrint("    Index Key Length: 0x%x\n", currentIndexExtry->KeyLength);
1587 
1588         // if this isn't the final (dummy) node, print info about the key (Filename attribute)
1589         if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END))
1590         {
1591             UNICODE_STRING Name;
1592             DbgPrint("     Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber);
1593             DbgPrint("     $FILENAME indexed: ");
1594             Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR);
1595             Name.MaximumLength = Name.Length;
1596             Name.Buffer = currentIndexExtry->FileName.Name;
1597             DbgPrint("'%wZ'\n", &Name);
1598         }
1599 
1600         // if this node has a sub-node beneath it
1601         if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE)
1602         {
1603             // Print the VCN of the sub-node
1604             PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG));
1605             DbgPrint("    VCN of sub-node: 0x%llx\n", *SubNodeVCN);
1606         }
1607 
1608         CurrentOffset += currentIndexExtry->Length;
1609         ASSERT(currentIndexExtry->Length);
1610     }
1611 
1612 }
1613 
1614 
1615 static
1616 VOID
1617 NtfsDumpAttribute(PDEVICE_EXTENSION Vcb,
1618                   PNTFS_ATTR_RECORD Attribute)
1619 {
1620     UNICODE_STRING Name;
1621 
1622     ULONGLONG lcn = 0;
1623     ULONGLONG runcount = 0;
1624 
1625     switch (Attribute->Type)
1626     {
1627         case AttributeFileName:
1628             NtfsDumpFileNameAttribute(Attribute);
1629             break;
1630 
1631         case AttributeStandardInformation:
1632             NtfsDumpStandardInformationAttribute(Attribute);
1633             break;
1634 
1635         case AttributeObjectId:
1636             DbgPrint("  $OBJECT_ID ");
1637             break;
1638 
1639         case AttributeSecurityDescriptor:
1640             DbgPrint("  $SECURITY_DESCRIPTOR ");
1641             break;
1642 
1643         case AttributeVolumeName:
1644             NtfsDumpVolumeNameAttribute(Attribute);
1645             break;
1646 
1647         case AttributeVolumeInformation:
1648             NtfsDumpVolumeInformationAttribute(Attribute);
1649             break;
1650 
1651         case AttributeData:
1652             DbgPrint("  $DATA ");
1653             //DataBuf = ExAllocatePool(NonPagedPool,AttributeLengthAllocated(Attribute));
1654             break;
1655 
1656         case AttributeIndexRoot:
1657             NtfsDumpIndexRootAttribute(Attribute);
1658             break;
1659 
1660         case AttributeIndexAllocation:
1661             DbgPrint("  $INDEX_ALLOCATION ");
1662             break;
1663 
1664         case AttributeBitmap:
1665             DbgPrint("  $BITMAP ");
1666             break;
1667 
1668         case AttributeReparsePoint:
1669             DbgPrint("  $REPARSE_POINT ");
1670             break;
1671 
1672         case AttributeEAInformation:
1673             DbgPrint("  $EA_INFORMATION ");
1674             break;
1675 
1676         case AttributeEA:
1677             DbgPrint("  $EA ");
1678             break;
1679 
1680         case AttributePropertySet:
1681             DbgPrint("  $PROPERTY_SET ");
1682             break;
1683 
1684         case AttributeLoggedUtilityStream:
1685             DbgPrint("  $LOGGED_UTILITY_STREAM ");
1686             break;
1687 
1688         default:
1689             DbgPrint("  Attribute %lx ",
1690                      Attribute->Type);
1691             break;
1692     }
1693 
1694     if (Attribute->Type != AttributeAttributeList)
1695     {
1696         if (Attribute->NameLength != 0)
1697         {
1698             Name.Length = Attribute->NameLength * sizeof(WCHAR);
1699             Name.MaximumLength = Name.Length;
1700             Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset);
1701 
1702             DbgPrint("'%wZ' ", &Name);
1703         }
1704 
1705         DbgPrint("(%s)\n",
1706                  Attribute->IsNonResident ? "non-resident" : "resident");
1707 
1708         if (Attribute->IsNonResident)
1709         {
1710             FindRun(Attribute,0,&lcn, &runcount);
1711 
1712             DbgPrint("  AllocatedSize %I64u  DataSize %I64u InitilizedSize %I64u\n",
1713                      Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize, Attribute->NonResident.InitializedSize);
1714             DbgPrint("  logical clusters: %I64u - %I64u\n",
1715                      lcn, lcn + runcount - 1);
1716         }
1717         else
1718             DbgPrint("    %u bytes of data\n", Attribute->Resident.ValueLength);
1719     }
1720 }
1721 
1722 
1723 VOID NtfsDumpDataRunData(PUCHAR DataRun)
1724 {
1725     UCHAR DataRunOffsetSize;
1726     UCHAR DataRunLengthSize;
1727     CHAR i;
1728 
1729     DbgPrint("%02x ", *DataRun);
1730 
1731     if (*DataRun == 0)
1732         return;
1733 
1734     DataRunOffsetSize = (*DataRun >> 4) & 0xF;
1735     DataRunLengthSize = *DataRun & 0xF;
1736 
1737     DataRun++;
1738     for (i = 0; i < DataRunLengthSize; i++)
1739     {
1740         DbgPrint("%02x ", *DataRun);
1741         DataRun++;
1742     }
1743 
1744     for (i = 0; i < DataRunOffsetSize; i++)
1745     {
1746         DbgPrint("%02x ", *DataRun);
1747         DataRun++;
1748     }
1749 
1750     NtfsDumpDataRunData(DataRun);
1751 }
1752 
1753 
1754 VOID
1755 NtfsDumpDataRuns(PVOID StartOfRun,
1756                  ULONGLONG CurrentLCN)
1757 {
1758     PUCHAR DataRun = StartOfRun;
1759     LONGLONG DataRunOffset;
1760     ULONGLONG DataRunLength;
1761 
1762     if (CurrentLCN == 0)
1763     {
1764         DPRINT1("Dumping data runs.\n\tData:\n\t\t");
1765         NtfsDumpDataRunData(StartOfRun);
1766         DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n");
1767     }
1768 
1769     DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1770 
1771     if (DataRunOffset != -1)
1772         CurrentLCN += DataRunOffset;
1773 
1774     DbgPrint("\t\t%I64d\t", DataRunOffset);
1775     if (DataRunOffset < 99999)
1776         DbgPrint("\t");
1777     DbgPrint("%I64u\t", CurrentLCN);
1778     if (CurrentLCN < 99999)
1779         DbgPrint("\t");
1780     DbgPrint("%I64u\n", DataRunLength);
1781 
1782     if (*DataRun == 0)
1783         DbgPrint("\t\t00\n");
1784     else
1785         NtfsDumpDataRuns(DataRun, CurrentLCN);
1786 }
1787 
1788 
1789 VOID
1790 NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb,
1791                        PFILE_RECORD_HEADER FileRecord)
1792 {
1793     NTSTATUS Status;
1794     FIND_ATTR_CONTXT Context;
1795     PNTFS_ATTR_RECORD Attribute;
1796 
1797     Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1798     while (NT_SUCCESS(Status))
1799     {
1800         NtfsDumpAttribute(Vcb, Attribute);
1801 
1802         Status = FindNextAttribute(&Context, &Attribute);
1803     }
1804 
1805     FindCloseAttribute(&Context);
1806 }
1807 
1808 PFILENAME_ATTRIBUTE
1809 GetFileNameFromRecord(PDEVICE_EXTENSION Vcb,
1810                       PFILE_RECORD_HEADER FileRecord,
1811                       UCHAR NameType)
1812 {
1813     FIND_ATTR_CONTXT Context;
1814     PNTFS_ATTR_RECORD Attribute;
1815     PFILENAME_ATTRIBUTE Name;
1816     NTSTATUS Status;
1817 
1818     Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1819     while (NT_SUCCESS(Status))
1820     {
1821         if (Attribute->Type == AttributeFileName)
1822         {
1823             Name = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1824             if (Name->NameType == NameType ||
1825                 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_WIN32) ||
1826                 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_DOS))
1827             {
1828                 FindCloseAttribute(&Context);
1829                 return Name;
1830             }
1831         }
1832 
1833         Status = FindNextAttribute(&Context, &Attribute);
1834     }
1835 
1836     FindCloseAttribute(&Context);
1837     return NULL;
1838 }
1839 
1840 /**
1841 * GetPackedByteCount
1842 * Returns the minimum number of bytes needed to represent the value of a
1843 * 64-bit number. Used to encode data runs.
1844 */
1845 UCHAR
1846 GetPackedByteCount(LONGLONG NumberToPack,
1847                    BOOLEAN IsSigned)
1848 {
1849     if (!IsSigned)
1850     {
1851         if (NumberToPack >= 0x0100000000000000)
1852             return 8;
1853         if (NumberToPack >= 0x0001000000000000)
1854             return 7;
1855         if (NumberToPack >= 0x0000010000000000)
1856             return 6;
1857         if (NumberToPack >= 0x0000000100000000)
1858             return 5;
1859         if (NumberToPack >= 0x0000000001000000)
1860             return 4;
1861         if (NumberToPack >= 0x0000000000010000)
1862             return 3;
1863         if (NumberToPack >= 0x0000000000000100)
1864             return 2;
1865         return 1;
1866     }
1867 
1868     if (NumberToPack > 0)
1869     {
1870         // we have to make sure the number that gets encoded won't be interpreted as negative
1871         if (NumberToPack >= 0x0080000000000000)
1872             return 8;
1873         if (NumberToPack >= 0x0000800000000000)
1874             return 7;
1875         if (NumberToPack >= 0x0000008000000000)
1876             return 6;
1877         if (NumberToPack >= 0x0000000080000000)
1878             return 5;
1879         if (NumberToPack >= 0x0000000000800000)
1880             return 4;
1881         if (NumberToPack >= 0x0000000000008000)
1882             return 3;
1883         if (NumberToPack >= 0x0000000000000080)
1884             return 2;
1885     }
1886     else
1887     {
1888         // negative number
1889         if (NumberToPack <= 0xff80000000000000)
1890             return 8;
1891         if (NumberToPack <= 0xffff800000000000)
1892             return 7;
1893         if (NumberToPack <= 0xffffff8000000000)
1894             return 6;
1895         if (NumberToPack <= 0xffffffff80000000)
1896             return 5;
1897         if (NumberToPack <= 0xffffffffff800000)
1898             return 4;
1899         if (NumberToPack <= 0xffffffffffff8000)
1900             return 3;
1901         if (NumberToPack <= 0xffffffffffffff80)
1902             return 2;
1903     }
1904     return 1;
1905 }
1906 
1907 NTSTATUS
1908 GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster)
1909 {
1910     LONGLONG DataRunOffset;
1911     ULONGLONG DataRunLength;
1912     LONGLONG DataRunStartLCN;
1913 
1914     ULONGLONG LastLCN = 0;
1915     PUCHAR DataRun = (PUCHAR)Attribute + Attribute->NonResident.MappingPairsOffset;
1916 
1917     if (!Attribute->IsNonResident)
1918         return STATUS_INVALID_PARAMETER;
1919 
1920     while (1)
1921     {
1922         DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1923 
1924         if (DataRunOffset != -1)
1925         {
1926             // Normal data run.
1927             DataRunStartLCN = LastLCN + DataRunOffset;
1928             LastLCN = DataRunStartLCN;
1929             *LastCluster = LastLCN + DataRunLength - 1;
1930         }
1931 
1932         if (*DataRun == 0)
1933             break;
1934     }
1935 
1936     return STATUS_SUCCESS;
1937 }
1938 
1939 PSTANDARD_INFORMATION
1940 GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb,
1941                                  PFILE_RECORD_HEADER FileRecord)
1942 {
1943     NTSTATUS Status;
1944     FIND_ATTR_CONTXT Context;
1945     PNTFS_ATTR_RECORD Attribute;
1946     PSTANDARD_INFORMATION StdInfo;
1947 
1948     Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1949     while (NT_SUCCESS(Status))
1950     {
1951         if (Attribute->Type == AttributeStandardInformation)
1952         {
1953             StdInfo = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1954             FindCloseAttribute(&Context);
1955             return StdInfo;
1956         }
1957 
1958         Status = FindNextAttribute(&Context, &Attribute);
1959     }
1960 
1961     FindCloseAttribute(&Context);
1962     return NULL;
1963 }
1964 
1965 /**
1966 * @name GetFileNameAttributeLength
1967 * @implemented
1968 *
1969 * Returns the size of a given FILENAME_ATTRIBUTE, in bytes.
1970 *
1971 * @param FileNameAttribute
1972 * Pointer to a FILENAME_ATTRIBUTE to determine the size of.
1973 *
1974 * @remarks
1975 * The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end.
1976 * This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD.
1977 */
1978 ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute)
1979 {
1980     ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR));
1981     return Length;
1982 }
1983 
1984 PFILENAME_ATTRIBUTE
1985 GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb,
1986                           PFILE_RECORD_HEADER FileRecord)
1987 {
1988     PFILENAME_ATTRIBUTE FileName;
1989 
1990     FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_POSIX);
1991     if (FileName == NULL)
1992     {
1993         FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_WIN32);
1994         if (FileName == NULL)
1995         {
1996             FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_DOS);
1997         }
1998     }
1999 
2000     return FileName;
2001 }
2002 
2003 /* EOF */
2004