[NTFS] - Add some helper functions for new features. Add some fixes. Add support...
[reactos.git] / drivers / filesystems / ntfs / attrib.c
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 = ExAllocatePoolWithTag(NonPagedPool,
1087 Vcb->NtfsInfo.BytesPerFileRecord,
1088 TAG_NTFS);
1089 if (BitmapRecord == NULL)
1090 {
1091 DPRINT1("Error: Unable to allocate memory for bitmap file record!\n");
1092 return STATUS_NO_MEMORY;
1093 }
1094
1095 Status = ReadFileRecord(Vcb, NTFS_FILE_BITMAP, BitmapRecord);
1096 if (!NT_SUCCESS(Status))
1097 {
1098 DPRINT1("Error: Unable to read file record for bitmap!\n");
1099 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
1100 return 0;
1101 }
1102
1103 Status = FindAttribute(Vcb, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL);
1104 if (!NT_SUCCESS(Status))
1105 {
1106 DPRINT1("Error: Unable to find data attribute for bitmap file!\n");
1107 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
1108 return 0;
1109 }
1110
1111 BitmapDataSize = AttributeDataLength(DataContext->pRecord);
1112 BitmapDataSize = min(BitmapDataSize, ULONG_MAX);
1113 ASSERT((BitmapDataSize * 8) >= Vcb->NtfsInfo.ClusterCount);
1114 BitmapData = ExAllocatePoolWithTag(NonPagedPool, ROUND_UP(BitmapDataSize, Vcb->NtfsInfo.BytesPerSector), TAG_NTFS);
1115 if (BitmapData == NULL)
1116 {
1117 DPRINT1("Error: Unable to allocate memory for bitmap file data!\n");
1118 ReleaseAttributeContext(DataContext);
1119 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
1120 return 0;
1121 }
1122
1123 ReadAttribute(Vcb, DataContext, 0, (PCHAR)BitmapData, (ULONG)BitmapDataSize);
1124
1125 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, Vcb->NtfsInfo.ClusterCount);
1126
1127 // free clusters in $BITMAP file
1128 while (ClustersLeftToFree > 0)
1129 {
1130 LONGLONG LargeVbn, LargeLbn;
1131
1132 if (!FsRtlLookupLastLargeMcbEntry(&AttrContext->DataRunsMCB, &LargeVbn, &LargeLbn))
1133 {
1134 Status = STATUS_INVALID_PARAMETER;
1135 DPRINT1("DRIVER ERROR: FreeClusters called to free %lu clusters, which is %lu more clusters than are assigned to attribute!",
1136 ClustersToFree,
1137 ClustersLeftToFree);
1138 break;
1139 }
1140
1141 if (LargeLbn != -1)
1142 {
1143 // deallocate this cluster
1144 RtlClearBits(&Bitmap, LargeLbn, 1);
1145 }
1146 FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->pRecord->NonResident.HighestVCN);
1147
1148 // decrement HighestVCN, but don't let it go below 0
1149 AttrContext->pRecord->NonResident.HighestVCN = min(AttrContext->pRecord->NonResident.HighestVCN, AttrContext->pRecord->NonResident.HighestVCN - 1);
1150 ClustersLeftToFree--;
1151 }
1152
1153 // update $BITMAP file on disk
1154 Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, FileRecord);
1155 if (!NT_SUCCESS(Status))
1156 {
1157 ReleaseAttributeContext(DataContext);
1158 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1159 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
1160 return Status;
1161 }
1162
1163 ReleaseAttributeContext(DataContext);
1164 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1165 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
1166
1167 // Save updated data runs to file record
1168
1169 // Allocate some memory for a new RunBuffer
1170 RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1171 if (!RunBuffer)
1172 {
1173 DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
1174 return STATUS_INSUFFICIENT_RESOURCES;
1175 }
1176
1177 // Convert the map control block back to encoded data runs
1178 ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
1179
1180 // Update HighestVCN
1181 DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN;
1182
1183 // Write data runs to destination attribute
1184 RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
1185 RunBuffer,
1186 RunBufferSize);
1187
1188 // Is DestinationAttribute the last attribute in the file record?
1189 if (NextAttribute->Type == AttributeEnd)
1190 {
1191 // update attribute length
1192 DestinationAttribute->Length = ALIGN_UP_BY(AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize,
1193 ATTR_RECORD_ALIGNMENT);
1194
1195 ASSERT(DestinationAttribute->Length <= AttrContext->pRecord->Length);
1196
1197 AttrContext->pRecord->Length = DestinationAttribute->Length;
1198
1199 // write end markers
1200 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
1201 SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
1202 }
1203
1204 // Update the file record
1205 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
1206
1207 ExFreePoolWithTag(RunBuffer, TAG_NTFS);
1208
1209 NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
1210
1211 return Status;
1212 }
1213
1214 static
1215 NTSTATUS
1216 InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context)
1217 {
1218 ULONGLONG ListSize;
1219 PNTFS_ATTR_RECORD Attribute;
1220 PNTFS_ATTR_CONTEXT ListContext;
1221
1222 DPRINT("InternalReadNonResidentAttributes(%p)\n", Context);
1223
1224 Attribute = Context->CurrAttr;
1225 ASSERT(Attribute->Type == AttributeAttributeList);
1226
1227 if (Context->OnlyResident)
1228 {
1229 Context->NonResidentStart = NULL;
1230 Context->NonResidentEnd = NULL;
1231 return STATUS_SUCCESS;
1232 }
1233
1234 if (Context->NonResidentStart != NULL)
1235 {
1236 return STATUS_FILE_CORRUPT_ERROR;
1237 }
1238
1239 ListContext = PrepareAttributeContext(Attribute);
1240 ListSize = AttributeDataLength(ListContext->pRecord);
1241 if (ListSize > 0xFFFFFFFF)
1242 {
1243 ReleaseAttributeContext(ListContext);
1244 return STATUS_BUFFER_OVERFLOW;
1245 }
1246
1247 Context->NonResidentStart = ExAllocatePoolWithTag(NonPagedPool, (ULONG)ListSize, TAG_NTFS);
1248 if (Context->NonResidentStart == NULL)
1249 {
1250 ReleaseAttributeContext(ListContext);
1251 return STATUS_INSUFFICIENT_RESOURCES;
1252 }
1253
1254 if (ReadAttribute(Context->Vcb, ListContext, 0, (PCHAR)Context->NonResidentStart, (ULONG)ListSize) != ListSize)
1255 {
1256 ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
1257 Context->NonResidentStart = NULL;
1258 ReleaseAttributeContext(ListContext);
1259 return STATUS_FILE_CORRUPT_ERROR;
1260 }
1261
1262 ReleaseAttributeContext(ListContext);
1263 Context->NonResidentEnd = (PNTFS_ATTR_RECORD)((PCHAR)Context->NonResidentStart + ListSize);
1264 return STATUS_SUCCESS;
1265 }
1266
1267 static
1268 PNTFS_ATTR_RECORD
1269 InternalGetNextAttribute(PFIND_ATTR_CONTXT Context)
1270 {
1271 PNTFS_ATTR_RECORD NextAttribute;
1272
1273 if (Context->CurrAttr == (PVOID)-1)
1274 {
1275 return NULL;
1276 }
1277
1278 if (Context->CurrAttr >= Context->FirstAttr &&
1279 Context->CurrAttr < Context->LastAttr)
1280 {
1281 if (Context->CurrAttr->Length == 0)
1282 {
1283 DPRINT1("Broken length!\n");
1284 Context->CurrAttr = (PVOID)-1;
1285 return NULL;
1286 }
1287
1288 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
1289
1290 if (NextAttribute > Context->LastAttr || NextAttribute < Context->FirstAttr)
1291 {
1292 DPRINT1("Broken length: 0x%lx!\n", Context->CurrAttr->Length);
1293 Context->CurrAttr = (PVOID)-1;
1294 return NULL;
1295 }
1296
1297 Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
1298 Context->CurrAttr = NextAttribute;
1299
1300 if (Context->CurrAttr < Context->LastAttr &&
1301 Context->CurrAttr->Type != AttributeEnd)
1302 {
1303 return Context->CurrAttr;
1304 }
1305 }
1306
1307 if (Context->NonResidentStart == NULL)
1308 {
1309 Context->CurrAttr = (PVOID)-1;
1310 return NULL;
1311 }
1312
1313 if (Context->CurrAttr < Context->NonResidentStart ||
1314 Context->CurrAttr >= Context->NonResidentEnd)
1315 {
1316 Context->CurrAttr = Context->NonResidentStart;
1317 }
1318 else if (Context->CurrAttr->Length != 0)
1319 {
1320 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
1321 Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
1322 Context->CurrAttr = NextAttribute;
1323 }
1324 else
1325 {
1326 DPRINT1("Broken length!\n");
1327 Context->CurrAttr = (PVOID)-1;
1328 return NULL;
1329 }
1330
1331 if (Context->CurrAttr < Context->NonResidentEnd &&
1332 Context->CurrAttr->Type != AttributeEnd)
1333 {
1334 return Context->CurrAttr;
1335 }
1336
1337 Context->CurrAttr = (PVOID)-1;
1338 return NULL;
1339 }
1340
1341 NTSTATUS
1342 FindFirstAttribute(PFIND_ATTR_CONTXT Context,
1343 PDEVICE_EXTENSION Vcb,
1344 PFILE_RECORD_HEADER FileRecord,
1345 BOOLEAN OnlyResident,
1346 PNTFS_ATTR_RECORD * Attribute)
1347 {
1348 NTSTATUS Status;
1349
1350 DPRINT("FindFistAttribute(%p, %p, %p, %p, %u, %p)\n", Context, Vcb, FileRecord, OnlyResident, Attribute);
1351
1352 Context->Vcb = Vcb;
1353 Context->OnlyResident = OnlyResident;
1354 Context->FirstAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
1355 Context->CurrAttr = Context->FirstAttr;
1356 Context->LastAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse);
1357 Context->NonResidentStart = NULL;
1358 Context->NonResidentEnd = NULL;
1359 Context->Offset = FileRecord->AttributeOffset;
1360
1361 if (Context->FirstAttr->Type == AttributeEnd)
1362 {
1363 Context->CurrAttr = (PVOID)-1;
1364 return STATUS_END_OF_FILE;
1365 }
1366 else if (Context->FirstAttr->Type == AttributeAttributeList)
1367 {
1368 Status = InternalReadNonResidentAttributes(Context);
1369 if (!NT_SUCCESS(Status))
1370 {
1371 return Status;
1372 }
1373
1374 *Attribute = InternalGetNextAttribute(Context);
1375 if (*Attribute == NULL)
1376 {
1377 return STATUS_END_OF_FILE;
1378 }
1379 }
1380 else
1381 {
1382 *Attribute = Context->CurrAttr;
1383 Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord;
1384 }
1385
1386 return STATUS_SUCCESS;
1387 }
1388
1389 NTSTATUS
1390 FindNextAttribute(PFIND_ATTR_CONTXT Context,
1391 PNTFS_ATTR_RECORD * Attribute)
1392 {
1393 NTSTATUS Status;
1394
1395 DPRINT("FindNextAttribute(%p, %p)\n", Context, Attribute);
1396
1397 *Attribute = InternalGetNextAttribute(Context);
1398 if (*Attribute == NULL)
1399 {
1400 return STATUS_END_OF_FILE;
1401 }
1402
1403 if (Context->CurrAttr->Type != AttributeAttributeList)
1404 {
1405 return STATUS_SUCCESS;
1406 }
1407
1408 Status = InternalReadNonResidentAttributes(Context);
1409 if (!NT_SUCCESS(Status))
1410 {
1411 return Status;
1412 }
1413
1414 *Attribute = InternalGetNextAttribute(Context);
1415 if (*Attribute == NULL)
1416 {
1417 return STATUS_END_OF_FILE;
1418 }
1419
1420 return STATUS_SUCCESS;
1421 }
1422
1423 VOID
1424 FindCloseAttribute(PFIND_ATTR_CONTXT Context)
1425 {
1426 if (Context->NonResidentStart != NULL)
1427 {
1428 ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
1429 Context->NonResidentStart = NULL;
1430 }
1431 }
1432
1433 static
1434 VOID
1435 NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute)
1436 {
1437 PFILENAME_ATTRIBUTE FileNameAttr;
1438
1439 DbgPrint(" $FILE_NAME ");
1440
1441 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1442
1443 FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1444 DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name);
1445 DbgPrint(" '%x' \n", FileNameAttr->FileAttributes);
1446 DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize);
1447 DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber);
1448 }
1449
1450
1451 static
1452 VOID
1453 NtfsDumpStandardInformationAttribute(PNTFS_ATTR_RECORD Attribute)
1454 {
1455 PSTANDARD_INFORMATION StandardInfoAttr;
1456
1457 DbgPrint(" $STANDARD_INFORMATION ");
1458
1459 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1460
1461 StandardInfoAttr = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1462 DbgPrint(" '%x' ", StandardInfoAttr->FileAttribute);
1463 }
1464
1465
1466 static
1467 VOID
1468 NtfsDumpVolumeNameAttribute(PNTFS_ATTR_RECORD Attribute)
1469 {
1470 PWCHAR VolumeName;
1471
1472 DbgPrint(" $VOLUME_NAME ");
1473
1474 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1475
1476 VolumeName = (PWCHAR)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1477 DbgPrint(" '%.*S' ", Attribute->Resident.ValueLength / sizeof(WCHAR), VolumeName);
1478 }
1479
1480
1481 static
1482 VOID
1483 NtfsDumpVolumeInformationAttribute(PNTFS_ATTR_RECORD Attribute)
1484 {
1485 PVOLINFO_ATTRIBUTE VolInfoAttr;
1486
1487 DbgPrint(" $VOLUME_INFORMATION ");
1488
1489 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1490
1491 VolInfoAttr = (PVOLINFO_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1492 DbgPrint(" NTFS Version %u.%u Flags 0x%04hx ",
1493 VolInfoAttr->MajorVersion,
1494 VolInfoAttr->MinorVersion,
1495 VolInfoAttr->Flags);
1496 }
1497
1498
1499 static
1500 VOID
1501 NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute)
1502 {
1503 PINDEX_ROOT_ATTRIBUTE IndexRootAttr;
1504 ULONG CurrentOffset;
1505 ULONG CurrentNode;
1506
1507 IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1508
1509 if (IndexRootAttr->AttributeType == AttributeFileName)
1510 ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME);
1511
1512 DbgPrint(" $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
1513
1514 if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL)
1515 {
1516 DbgPrint(" (small)\n");
1517 }
1518 else
1519 {
1520 ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE);
1521 DbgPrint(" (large)\n");
1522 }
1523
1524 DbgPrint(" Offset to first index: 0x%lx\n Total size of index entries: 0x%lx\n Allocated size of node: 0x%lx\n",
1525 IndexRootAttr->Header.FirstEntryOffset,
1526 IndexRootAttr->Header.TotalSizeOfEntries,
1527 IndexRootAttr->Header.AllocatedSize);
1528 CurrentOffset = IndexRootAttr->Header.FirstEntryOffset;
1529 CurrentNode = 0;
1530 // print details of every node in the index
1531 while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries)
1532 {
1533 PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + CurrentOffset);
1534 DbgPrint(" Index Node Entry %lu", CurrentNode++);
1535 if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_NODE))
1536 DbgPrint(" (Branch)");
1537 else
1538 DbgPrint(" (Leaf)");
1539 if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END))
1540 {
1541 DbgPrint(" (Dummy Key)");
1542 }
1543 DbgPrint("\n File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile);
1544 DbgPrint(" Index Entry Length: 0x%x\n", currentIndexExtry->Length);
1545 DbgPrint(" Index Key Length: 0x%x\n", currentIndexExtry->KeyLength);
1546
1547 // if this isn't the final (dummy) node, print info about the key (Filename attribute)
1548 if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END))
1549 {
1550 UNICODE_STRING Name;
1551 DbgPrint(" Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber);
1552 DbgPrint(" $FILENAME indexed: ");
1553 Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR);
1554 Name.MaximumLength = Name.Length;
1555 Name.Buffer = currentIndexExtry->FileName.Name;
1556 DbgPrint("'%wZ'\n", &Name);
1557 }
1558
1559 // if this node has a sub-node beneath it
1560 if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE)
1561 {
1562 // Print the VCN of the sub-node
1563 PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG));
1564 DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN);
1565 }
1566
1567 CurrentOffset += currentIndexExtry->Length;
1568 ASSERT(currentIndexExtry->Length);
1569 }
1570
1571 }
1572
1573
1574 static
1575 VOID
1576 NtfsDumpAttribute(PDEVICE_EXTENSION Vcb,
1577 PNTFS_ATTR_RECORD Attribute)
1578 {
1579 UNICODE_STRING Name;
1580
1581 ULONGLONG lcn = 0;
1582 ULONGLONG runcount = 0;
1583
1584 switch (Attribute->Type)
1585 {
1586 case AttributeFileName:
1587 NtfsDumpFileNameAttribute(Attribute);
1588 break;
1589
1590 case AttributeStandardInformation:
1591 NtfsDumpStandardInformationAttribute(Attribute);
1592 break;
1593
1594 case AttributeObjectId:
1595 DbgPrint(" $OBJECT_ID ");
1596 break;
1597
1598 case AttributeSecurityDescriptor:
1599 DbgPrint(" $SECURITY_DESCRIPTOR ");
1600 break;
1601
1602 case AttributeVolumeName:
1603 NtfsDumpVolumeNameAttribute(Attribute);
1604 break;
1605
1606 case AttributeVolumeInformation:
1607 NtfsDumpVolumeInformationAttribute(Attribute);
1608 break;
1609
1610 case AttributeData:
1611 DbgPrint(" $DATA ");
1612 //DataBuf = ExAllocatePool(NonPagedPool,AttributeLengthAllocated(Attribute));
1613 break;
1614
1615 case AttributeIndexRoot:
1616 NtfsDumpIndexRootAttribute(Attribute);
1617 break;
1618
1619 case AttributeIndexAllocation:
1620 DbgPrint(" $INDEX_ALLOCATION ");
1621 break;
1622
1623 case AttributeBitmap:
1624 DbgPrint(" $BITMAP ");
1625 break;
1626
1627 case AttributeReparsePoint:
1628 DbgPrint(" $REPARSE_POINT ");
1629 break;
1630
1631 case AttributeEAInformation:
1632 DbgPrint(" $EA_INFORMATION ");
1633 break;
1634
1635 case AttributeEA:
1636 DbgPrint(" $EA ");
1637 break;
1638
1639 case AttributePropertySet:
1640 DbgPrint(" $PROPERTY_SET ");
1641 break;
1642
1643 case AttributeLoggedUtilityStream:
1644 DbgPrint(" $LOGGED_UTILITY_STREAM ");
1645 break;
1646
1647 default:
1648 DbgPrint(" Attribute %lx ",
1649 Attribute->Type);
1650 break;
1651 }
1652
1653 if (Attribute->Type != AttributeAttributeList)
1654 {
1655 if (Attribute->NameLength != 0)
1656 {
1657 Name.Length = Attribute->NameLength * sizeof(WCHAR);
1658 Name.MaximumLength = Name.Length;
1659 Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset);
1660
1661 DbgPrint("'%wZ' ", &Name);
1662 }
1663
1664 DbgPrint("(%s)\n",
1665 Attribute->IsNonResident ? "non-resident" : "resident");
1666
1667 if (Attribute->IsNonResident)
1668 {
1669 FindRun(Attribute,0,&lcn, &runcount);
1670
1671 DbgPrint(" AllocatedSize %I64u DataSize %I64u InitilizedSize %I64u\n",
1672 Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize, Attribute->NonResident.InitializedSize);
1673 DbgPrint(" logical clusters: %I64u - %I64u\n",
1674 lcn, lcn + runcount - 1);
1675 }
1676 else
1677 DbgPrint(" %u bytes of data\n", Attribute->Resident.ValueLength);
1678 }
1679 }
1680
1681
1682 VOID NtfsDumpDataRunData(PUCHAR DataRun)
1683 {
1684 UCHAR DataRunOffsetSize;
1685 UCHAR DataRunLengthSize;
1686 CHAR i;
1687
1688 DbgPrint("%02x ", *DataRun);
1689
1690 if (*DataRun == 0)
1691 return;
1692
1693 DataRunOffsetSize = (*DataRun >> 4) & 0xF;
1694 DataRunLengthSize = *DataRun & 0xF;
1695
1696 DataRun++;
1697 for (i = 0; i < DataRunLengthSize; i++)
1698 {
1699 DbgPrint("%02x ", *DataRun);
1700 DataRun++;
1701 }
1702
1703 for (i = 0; i < DataRunOffsetSize; i++)
1704 {
1705 DbgPrint("%02x ", *DataRun);
1706 DataRun++;
1707 }
1708
1709 NtfsDumpDataRunData(DataRun);
1710 }
1711
1712
1713 VOID
1714 NtfsDumpDataRuns(PVOID StartOfRun,
1715 ULONGLONG CurrentLCN)
1716 {
1717 PUCHAR DataRun = StartOfRun;
1718 LONGLONG DataRunOffset;
1719 ULONGLONG DataRunLength;
1720
1721 if (CurrentLCN == 0)
1722 {
1723 DPRINT1("Dumping data runs.\n\tData:\n\t\t");
1724 NtfsDumpDataRunData(StartOfRun);
1725 DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n");
1726 }
1727
1728 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1729
1730 if (DataRunOffset != -1)
1731 CurrentLCN += DataRunOffset;
1732
1733 DbgPrint("\t\t%I64d\t", DataRunOffset);
1734 if (DataRunOffset < 99999)
1735 DbgPrint("\t");
1736 DbgPrint("%I64u\t", CurrentLCN);
1737 if (CurrentLCN < 99999)
1738 DbgPrint("\t");
1739 DbgPrint("%I64u\n", DataRunLength);
1740
1741 if (*DataRun == 0)
1742 DbgPrint("\t\t00\n");
1743 else
1744 NtfsDumpDataRuns(DataRun, CurrentLCN);
1745 }
1746
1747
1748 VOID
1749 NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb,
1750 PFILE_RECORD_HEADER FileRecord)
1751 {
1752 NTSTATUS Status;
1753 FIND_ATTR_CONTXT Context;
1754 PNTFS_ATTR_RECORD Attribute;
1755
1756 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1757 while (NT_SUCCESS(Status))
1758 {
1759 NtfsDumpAttribute(Vcb, Attribute);
1760
1761 Status = FindNextAttribute(&Context, &Attribute);
1762 }
1763
1764 FindCloseAttribute(&Context);
1765 }
1766
1767 PFILENAME_ATTRIBUTE
1768 GetFileNameFromRecord(PDEVICE_EXTENSION Vcb,
1769 PFILE_RECORD_HEADER FileRecord,
1770 UCHAR NameType)
1771 {
1772 FIND_ATTR_CONTXT Context;
1773 PNTFS_ATTR_RECORD Attribute;
1774 PFILENAME_ATTRIBUTE Name;
1775 NTSTATUS Status;
1776
1777 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1778 while (NT_SUCCESS(Status))
1779 {
1780 if (Attribute->Type == AttributeFileName)
1781 {
1782 Name = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1783 if (Name->NameType == NameType ||
1784 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_WIN32) ||
1785 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_DOS))
1786 {
1787 FindCloseAttribute(&Context);
1788 return Name;
1789 }
1790 }
1791
1792 Status = FindNextAttribute(&Context, &Attribute);
1793 }
1794
1795 FindCloseAttribute(&Context);
1796 return NULL;
1797 }
1798
1799 /**
1800 * GetPackedByteCount
1801 * Returns the minimum number of bytes needed to represent the value of a
1802 * 64-bit number. Used to encode data runs.
1803 */
1804 UCHAR
1805 GetPackedByteCount(LONGLONG NumberToPack,
1806 BOOLEAN IsSigned)
1807 {
1808 if (!IsSigned)
1809 {
1810 if (NumberToPack >= 0x0100000000000000)
1811 return 8;
1812 if (NumberToPack >= 0x0001000000000000)
1813 return 7;
1814 if (NumberToPack >= 0x0000010000000000)
1815 return 6;
1816 if (NumberToPack >= 0x0000000100000000)
1817 return 5;
1818 if (NumberToPack >= 0x0000000001000000)
1819 return 4;
1820 if (NumberToPack >= 0x0000000000010000)
1821 return 3;
1822 if (NumberToPack >= 0x0000000000000100)
1823 return 2;
1824 return 1;
1825 }
1826
1827 if (NumberToPack > 0)
1828 {
1829 // we have to make sure the number that gets encoded won't be interpreted as negative
1830 if (NumberToPack >= 0x0080000000000000)
1831 return 8;
1832 if (NumberToPack >= 0x0000800000000000)
1833 return 7;
1834 if (NumberToPack >= 0x0000008000000000)
1835 return 6;
1836 if (NumberToPack >= 0x0000000080000000)
1837 return 5;
1838 if (NumberToPack >= 0x0000000000800000)
1839 return 4;
1840 if (NumberToPack >= 0x0000000000008000)
1841 return 3;
1842 if (NumberToPack >= 0x0000000000000080)
1843 return 2;
1844 }
1845 else
1846 {
1847 // negative number
1848 if (NumberToPack <= 0xff80000000000000)
1849 return 8;
1850 if (NumberToPack <= 0xffff800000000000)
1851 return 7;
1852 if (NumberToPack <= 0xffffff8000000000)
1853 return 6;
1854 if (NumberToPack <= 0xffffffff80000000)
1855 return 5;
1856 if (NumberToPack <= 0xffffffffff800000)
1857 return 4;
1858 if (NumberToPack <= 0xffffffffffff8000)
1859 return 3;
1860 if (NumberToPack <= 0xffffffffffffff80)
1861 return 2;
1862 }
1863 return 1;
1864 }
1865
1866 NTSTATUS
1867 GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster)
1868 {
1869 LONGLONG DataRunOffset;
1870 ULONGLONG DataRunLength;
1871 LONGLONG DataRunStartLCN;
1872
1873 ULONGLONG LastLCN = 0;
1874 PUCHAR DataRun = (PUCHAR)Attribute + Attribute->NonResident.MappingPairsOffset;
1875
1876 if (!Attribute->IsNonResident)
1877 return STATUS_INVALID_PARAMETER;
1878
1879 while (1)
1880 {
1881 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1882
1883 if (DataRunOffset != -1)
1884 {
1885 // Normal data run.
1886 DataRunStartLCN = LastLCN + DataRunOffset;
1887 LastLCN = DataRunStartLCN;
1888 *LastCluster = LastLCN + DataRunLength - 1;
1889 }
1890
1891 if (*DataRun == 0)
1892 break;
1893 }
1894
1895 return STATUS_SUCCESS;
1896 }
1897
1898 PSTANDARD_INFORMATION
1899 GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb,
1900 PFILE_RECORD_HEADER FileRecord)
1901 {
1902 NTSTATUS Status;
1903 FIND_ATTR_CONTXT Context;
1904 PNTFS_ATTR_RECORD Attribute;
1905 PSTANDARD_INFORMATION StdInfo;
1906
1907 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1908 while (NT_SUCCESS(Status))
1909 {
1910 if (Attribute->Type == AttributeStandardInformation)
1911 {
1912 StdInfo = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1913 FindCloseAttribute(&Context);
1914 return StdInfo;
1915 }
1916
1917 Status = FindNextAttribute(&Context, &Attribute);
1918 }
1919
1920 FindCloseAttribute(&Context);
1921 return NULL;
1922 }
1923
1924 /**
1925 * @name GetFileNameAttributeLength
1926 * @implemented
1927 *
1928 * Returns the size of a given FILENAME_ATTRIBUTE, in bytes.
1929 *
1930 * @param FileNameAttribute
1931 * Pointer to a FILENAME_ATTRIBUTE to determine the size of.
1932 *
1933 * @remarks
1934 * The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end.
1935 * This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD.
1936 */
1937 ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute)
1938 {
1939 ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR));
1940 return Length;
1941 }
1942
1943 PFILENAME_ATTRIBUTE
1944 GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb,
1945 PFILE_RECORD_HEADER FileRecord)
1946 {
1947 PFILENAME_ATTRIBUTE FileName;
1948
1949 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_POSIX);
1950 if (FileName == NULL)
1951 {
1952 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_WIN32);
1953 if (FileName == NULL)
1954 {
1955 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_DOS);
1956 }
1957 }
1958
1959 return FileName;
1960 }
1961
1962 /* EOF */