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