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