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