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