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