[NTFS] Correctly find attributes stored in another file record in MFT (and referenced...
[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 = 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 */