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